Problem while launching service server and service client at the same time

Hello,

I am working on the Rosject of the ROS2 In 5 Days Python course. I created and tested both the wall_following node and the wall_finding service separately using the terminal to launch the controller and to call the service server and both scripts are working as expected. However, my current task is to launch both the the service server node and the robot controller (the ‘wall_following’) node at the same time. In addition I have to modify the ‘wall_following’ node in order to create a synchronous service client to call the ‘wall_finding’ service server. But I am having trouble to make the code work. I am not sure what I am doing wrong. But I think there is a problem with the way I am sending the synchronous call in my main function and checking for the response in my ‘motion’ callback function.
My robot controller code is below:

import sys
from threading import Thread

# import python ros client
import rclpy
# import the ROS2 python libraries
from rclpy.node import Node
# import Twist module from geometry_msgs.msg
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from custom_interface.srv import FindWall
from std_srvs.srv import Empty
# import Quality of Service library, to set the correct profile and reliability to read sensor data.
from rclpy.qos import ReliabilityPolicy, QoSProfile


class Wall_Following(Node):

    def __init__(self):
        # naming the class constructor (node name)
        super().__init__('wall_following')

        # creating client for the /find_wall service server
        self.cli = self.create_client(FindWall, 'find_wall')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        # self.req = FindWall.Request()
        self.req = Empty.Request()

        # creating the publisher object
        self.publisher_ = self.create_publisher(Twist, '/cmd_vel', 10)
        # creating the subscriber object
        self.subscriber_ = self.create_subscription(
            LaserScan,
            '/scan',
            self.read_measurement,
            # is the most used to read LaserScan data and some sensor data.
            QoSProfile(depth=10, reliability=ReliabilityPolicy.BEST_EFFORT)
        )
        # define the timer period for 0.5 seconds
        self.timer_period = 0.5
        # declare variable to store all the laser messages
        self.laser_msg = LaserScan()
        # declare the variable to save the front laser scan reading
        self.forward_laser = 0.0
        # declare the variable to save the right laser scan reading
        self.right_laser = 0.0
        # declare the velocity twist message
        self.cmd = Twist()
        # create time
        self.timer = self.create_timer(self.timer_period, self.motion)

    def stop_robot(self):
        self.get_logger().info('STOPPING ROBOT...')
        self.cmd.linear.x = 0.0
        self.cmd.angular.z = 0.0
        self.publisher_.publish(self.cmd)

    def rotate_left(self):
        self.get_logger().info(
            f'Moving away from right wall - distance to right wall = {self.right_laser} m')
        self.cmd.linear.x = 0.1  # making sure that the robot is not moving forward
        self.cmd.angular.z = 0.2  # rotate counterclockwise to point to the closest wall
        self.publisher_.publish(self.cmd)

    def rotate_right(self):
        self.get_logger().info(
            f'Moving away from right wall - distance to right wall = {self.right_laser} m')
        self.cmd.linear.x = 0.1  # making sure that the robot is not moving forward
        self.cmd.angular.z = -0.2  # rotate counterclockwise to point to the closest wall
        self.publisher_.publish(self.cmd)

    def move_forward(self):
        self.get_logger().info('MOVING ROBOT FORWARD...')
        self.cmd.linear.x = 0.1
        self.cmd.angular.z = 0.0
        self.publisher_.publish(self.cmd)

    def hard_left_rotation(self):
        self.get_logger().info(
            f'Getting close to front wall - changing direction - distance to front wall = {self.forward_laser} m')
        self.cmd.linear.x = 0.05
        self.cmd.angular.z = 0.4
        self.publisher_.publish(self.cmd)

    def send_request(self):
        self.future = self.cli.call(self.req)

    def motion(self):

        if self.future.result():
            # check if the robot is far from the front wall
            if self.forward_laser > 0.5:
                # if the robot is far from the front wall and far from the right wall, move closer to the right wall
                if self.right_laser > 0.3:
                    self.rotate_right()
                # if the robot is far from the front wall and close to the right wall, move away from the right wall
                elif self.right_laser < 0.3:
                    self.rotate_left()
                # if the robot is far from the front wall is at a distance between 0.2 m and 0.3 m from the right wall, keep moving forward
                else:
                    self.move_forward()
            # if the robot is close to a front wall, increase angular velocity while still going forward
            else:
                self.hard_left_rotation()

            self.get_logger().info(
                f'Front laser measurement : {self.forward_laser} m')
            self.get_logger().info(
                f'Right laser measurement : {self.right_laser} m')
        else:
            self.get_logger().info('Server response is FALSE')

    def read_measurement(self, msg):
        self.laser_msg = msg
        self.forward_laser = self.laser_msg.ranges[359]
        self.right_laser = self.laser_msg.ranges[179]


def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    # declare the node constructor
    wall_following = Wall_Following()

    spin_thread = Thread(target=rclpy.spin, args=(wall_following,))
    spin_thread.start()

    wall_following.send_request()

    wall_following.destroy_node()
    # shutdown the ROS communication
    rclpy.shutdown()


if __name__ == '__main__':
    main()

Moreover, my launch file is the following:

from launch import LaunchDescription
from launch_ros.actions import Node


def generate_launch_description():
    return LaunchDescription([
        Node(
            package='wall_follower',
            executable='wall_finding',
            output='screen',
            arguments=['--ros-args', '--log-level', 'info']),

        Node(
            package='wall_follower',
            executable='wall_following_v2',
            output='screen',
            arguments=['--ros-args', '--log-level', 'info'])
    ])

And, this is the terminal output when I launch the launch file:

ros2 launch wall_follower main.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2022-12-25-11-32-42-413569-3_xterm-9801
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [wall_finding-1]: process started with pid [9898]
[INFO] [wall_following_v2-2]: process started with pid [9902]
[wall_finding-1] [INFO] [1671967965.949427122] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967966.243057828] [find_wall_service]: read laser measurement
[wall_following_v2-2] Traceback (most recent call last):
[wall_following_v2-2]   File "/home/user/ros2_ws/install/wall_follower/lib/wall_follower/wall_following_v2", line33, in <module>
[wall_following_v2-2]     sys.exit(load_entry_point('wall-follower==0.0.0', 'console_scripts', 'wall_following_v2')())
[wall_following_v2-2]   File "/home/user/ros2_ws/install/wall_follower/lib/python3.8/site-packages/wall_follower/wall_following_v2.py", line 174, in main
[wall_following_v2-2]     wall_following.send_request()
[wall_following_v2-2]   File "/home/user/ros2_ws/install/wall_follower/lib/python3.8/site-packages/wall_follower/wall_following_v2.py", line 132, in send_request
[wall_following_v2-2]     self.future = self.cli.call(self.req)
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/client.py", line 80, in call
[wall_following_v2-2]     raise TypeError()
[wall_following_v2-2] TypeError
[wall_following_v2-2] Exception in thread Thread-1:
[wall_following_v2-2] Traceback (most recent call last):
[wall_following_v2-2]   File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
[wall_following_v2-2]     self.run()
[wall_following_v2-2]   File "/usr/lib/python3.8/threading.py", line 870, in run
[wall_following_v2-2]     self._target(*self._args, **self._kwargs)
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/__init__.py", line 191, in spin
[wall_following_v2-2]     executor.spin_once()
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 711, in spin_once
[wall_following_v2-2]     raise handler.exception()
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/task.py", line 239, in __call__
[wall_following_v2-2]     self._handler.send(None)
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 426, in handler
[wall_following_v2-2]     await call_coroutine(entity, arg)
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 340, in _execute_timer
[wall_following_v2-2]     await await_or_execute(tmr.callback)
[wall_following_v2-2]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 118, in await_or_execute
[wall_following_v2-2]     return callback(*args)
[wall_following_v2-2]   File "/home/user/ros2_ws/install/wall_follower/lib/python3.8/site-packages/wall_follower/wall_following_v2.py", line 136, in motion
[wall_following_v2-2]     if self.future.result():
[wall_following_v2-2] AttributeError: 'Wall_Following' object has no attribute 'future'
[wall_finding-1] [INFO] [1671967966.762792541] [find_wall_service]: read laser measurement
[ERROR] [wall_following_v2-2]: process has died [pid 9902, exit code 1, cmd '/home/user/ros2_ws/install/wall_follower/lib/wall_follower/wall_following_v2 --ros-args --log-level info --ros-args'].
[wall_finding-1] [INFO] [1671967967.110928746] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967967.442750430] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967967.744603054] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967968.077909535] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967968.373987608] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967968.648504300] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967969.051760631] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967969.335983051] [find_wall_service]: read laser measurement
[wall_finding-1] [INFO] [1671967969.622519798] [find_wall_service]: read laser measurement
^C[WARNING] [launch]: user interrupted with ctrl-c (SIGINT)
[wall_finding-1] Traceback (most recent call last):
[wall_finding-1]   File "/home/user/ros2_ws/install/wall_follower/lib/wall_follower/wall_finding", line 33, in <module>
[wall_finding-1]     sys.exit(load_entry_point('wall-follower==0.0.0', 'console_scripts', 'wall_finding')())
[wall_finding-1]   File "/home/user/ros2_ws/install/wall_follower/lib/python3.8/site-packages/wall_follower/wall_finder.py", line 148, in main
[wall_finding-1]     executor.spin()
[wall_finding-1]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 290, in spin
[wall_finding-1]     self.spin_once()
[wall_finding-1]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 756, in spin_once
[wall_finding-1]     self._spin_once_impl(timeout_sec)
[wall_finding-1]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 742, in _spin_once_impl
[wall_finding-1]     handler, entity, node = self.wait_for_ready_callbacks(
[wall_finding-1]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 689, in wait_for_ready_callbacks
[wall_finding-1]     return next(self._cb_iter)
[wall_finding-1]   File "/opt/ros/foxy/lib/python3.8/site-packages/rclpy/executors.py", line 586, in _wait_for_ready_callbacks
[wall_finding-1]     _rclpy.rclpy_wait(wait_set, timeout_nsec)
[wall_finding-1] KeyboardInterrupt
[ERROR] [wall_finding-1]: process has died [pid 9898, exit code -2, cmd '/home/user/ros2_ws/install/wall_follower/lib/wall_follower/wall_finding --ros-args --log-level info --ros-args'].

Thank you for your help

Hi @e.na.hatem ,

After going through your code, I firstly noticed the following line:

Are you experiencing the problem because you have the wrong line commented? You should use the FindWall service message. You are using Empty instead.

I also noticed that you are running the wall_following node as a separate thread. You need not do that if you make the service client as a node. So you would be able to add wall_following and findwall_service_client as two nodes into the MultiThreadedExecutor.
(You are also not using MultiThreadedExecutor - this is very important as you will require a minimum of 4 threads for the main program).

Why does your launch file have 2 versions of wall_follower? It should just have the wall_follower and the findwall_service_server programs (as of now). Later your launch file will also include the action_server node.

I did not go through your output much - I saw your code and expected to see a few errors - so that is not surprising.

Summary:

  1. Change service request message type and call service properly.
  2. Modify the main code to include MultiThreadedExecutor.
  3. Individually check if a service call to FindWall_Service_Server is working properly - call multiple times one-after-another (not simultaneously - since the service is synchronous).

Sorry, I could not help you much but just give you some tips form my experience.
Best way to check if your code is working is to check if each part is working individually.
Especially for service, create a service server and a client - to just test the service functionality.

Regards,
Girish

1 Like

Hello @girishkumar.kannan ,

Thank you for your response.

For this part of the code, I was experimenting with the Empty service interface since I was obtaining an error with the custom FindWall service interface. But in the end I got the same error with the Empty service interface too. So I modified the code again as you mentioned and I am now using the custom FindWall service interface.

Moreover, I was running the wall_following node as a separate thread because I was following the ROS2 tutorial for Synchronous vs Asynchronous service clients and this is how they did synchronous calls. I remodified the code as you mentioned and I am now using the MultiThreadedExecutor for the wall_following service client node as well (I was using it for the findwall_service_server node only since the code was getting stuck in the while loop and the laser measurements were not being updated in the service server callback function). However, I didn’t really understand why I would need to use the MultiThreadedExecutor for the service client node (wall_following). But either way, I did it just to test the code to see the results.

My launch file currently launches the wall_finding service server node and the wall_following_v2 service client node. I ended the name of the script with _v2 because that way I can still have a copy of the wall_following script that I created for the first section of the Rosject.

In addition, I tested the FindWall_Service_Server and I think the code works but it’s not very consistent (the control law is based on the closest measurement index coming from the laser sensor measurements which may overshoot from time to time even with + or - 3 degrees of error allowance). I have to develop it more to make it more robust (by using statistical methods to find the closest wall as you mentioned to me earlier in a previous post, is there any tutorial that you know of that can help me with this task ?) but for the moment I think it’s good enough to test the service server node.

So, I launched the service server and the service client nodes with the modifications as you mentioned and now the robot is currently using the service server to find the wall. However, the robot doesn’t start to follow the wall after the response from the service server is done. And, I am not really sure why. I added the motion callback function to the MultiThreadedExecutor but it is as if the callback function is not being run because I am not obtaining anything from the get_logger() in the terminal. You can find the modified code of the service client node below:

import sys
from threading import Thread

# import python ros client
import rclpy
# import the ROS2 python libraries
from rclpy.node import Node
# import Twist module from geometry_msgs.msg
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from custom_interface.srv import FindWall
from std_srvs.srv import Empty
# import Quality of Service library, to set the correct profile and reliability to read sensor data.
from rclpy.qos import ReliabilityPolicy, QoSProfile
from rclpy.callback_groups import ReentrantCallbackGroup, MutuallyExclusiveCallbackGroup
from rclpy.executors import MultiThreadedExecutor, SingleThreadedExecutor


class Wall_Following(Node):

    def __init__(self):
        # naming the class constructor (node name)
        super().__init__('wall_following')

        self.group = ReentrantCallbackGroup()

        # creating client for the /find_wall service server
        self.cli = self.create_client(
            FindWall, 'find_wall', callback_group=self.group)
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = FindWall.Request()
        # self.req = Empty.Request()

        # creating the publisher object
        self.publisher_ = self.create_publisher(Twist, '/cmd_vel', 10)
        # creating the subscriber object
        self.subscriber_ = self.create_subscription(
            LaserScan,
            '/scan',
            self.read_measurement,
            # is the most used to read LaserScan data and some sensor data.
            QoSProfile(depth=10, reliability=ReliabilityPolicy.BEST_EFFORT),
            callback_group=self.group
        )
        # define the timer period for 0.5 seconds
        self.timer_period = 0.5
        # declare variable to store all the laser messages
        self.laser_msg = LaserScan()
        # declare the variable to save the front laser scan reading
        self.forward_laser = 0.0
        # declare the variable to save the right laser scan reading
        self.right_laser = 0.0
        # declare the velocity twist message
        self.cmd = Twist()
        # create time
        self.timer = self.create_timer(
            self.timer_period, self.motion, callback_group=self.group)

    def stop_robot(self):
        self.get_logger().info('STOPPING ROBOT...')
        self.cmd.linear.x = 0.0
        self.cmd.angular.z = 0.0
        self.publisher_.publish(self.cmd)

    def rotate_left(self):
        self.get_logger().info(
            f'Moving away from right wall - distance to right wall = {self.right_laser} m')
        self.cmd.linear.x = 0.1  # making sure that the robot is not moving forward
        self.cmd.angular.z = 0.2  # rotate counterclockwise to point to the closest wall
        self.publisher_.publish(self.cmd)

    def rotate_right(self):
        self.get_logger().info(
            f'Moving away from right wall - distance to right wall = {self.right_laser} m')
        self.cmd.linear.x = 0.1  # making sure that the robot is not moving forward
        self.cmd.angular.z = -0.2  # rotate counterclockwise to point to the closest wall
        self.publisher_.publish(self.cmd)

    def move_forward(self):
        self.get_logger().info('MOVING ROBOT FORWARD...')
        self.cmd.linear.x = 0.1
        self.cmd.angular.z = 0.0
        self.publisher_.publish(self.cmd)

    def hard_left_rotation(self):
        self.get_logger().info(
            f'Getting close to front wall - changing direction - distance to front wall = {self.forward_laser} m')
        self.cmd.linear.x = 0.05
        self.cmd.angular.z = 0.4
        self.publisher_.publish(self.cmd)

    def send_request(self):
        self.get_logger().info('SENDING REQUEST...')
        self.future = self.cli.call(self.req)

    def motion(self):

        if self.future.result() is True:
            # check if the robot is far from the front wall
            if self.forward_laser > 0.5:
                # if the robot is far from the front wall and far from the right wall, move closer to the right wall
                if self.right_laser > 0.3:
                    self.rotate_right()
                # if the robot is far from the front wall and close to the right wall, move away from the right wall
                elif self.right_laser < 0.3:
                    self.rotate_left()
                # if the robot is far from the front wall is at a distance between 0.2 m and 0.3 m from the right wall, keep moving forward
                else:
                    self.move_forward()
            # if the robot is close to a front wall, increase angular velocity while still going forward
            else:
                self.hard_left_rotation()

            self.get_logger().info(
                f'Front laser measurement : {self.forward_laser} m')
            self.get_logger().info(
                f'Right laser measurement : {self.right_laser} m')
        else:
            self.get_logger().info('Server response is FALSE')

    def read_measurement(self, msg):
        self.laser_msg = msg
        self.forward_laser = self.laser_msg.ranges[359]
        self.right_laser = self.laser_msg.ranges[179]


# def main(args=None):
#     # initialize the ROS communication
#     rclpy.init(args=args)
#     # declare the node constructor
#     wall_following = Wall_Following()

#     spin_thread = Thread(target=rclpy.spin, args=(wall_following,))
#     spin_thread.start()

#     wall_following.send_request()

#     wall_following.destroy_node()
#     # shutdown the ROS communication
#     rclpy.shutdown()

def main(args=None):  # check the main function of a service node
    rclpy.init(args=args)
    try:
        wall_following = Wall_Following()
        wall_following.send_request()
        executor = MultiThreadedExecutor(num_threads=6)
        executor.add_node(wall_following)
        try:
            executor.spin()
        finally:
            executor.shutdown()
            wall_following.destroy_node()

    finally:
        rclpy.shutdown()


if __name__ == '__main__':
    main()

You can also find the final output from the terminal as well as an image showing the final position of the robot after the service client response is received:

[wall_finding-1] [INFO] [1672141465.425518444] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.426367581] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.427212142] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.429122142] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.511971553] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.520218935] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.522537664] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.523457028] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.525037160] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.525851671] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.533478629] [find_wall_service]: Closest measurement index = 197
[wall_finding-1] [INFO] [1672141465.534953856] [find_wall_service]: Closest measurement index = 180
[wall_finding-1] [INFO] [1672141465.536143311] [find_wall_service]: The closest wallis currently on the right - Stopping robot...
[wall_finding-1] [INFO] [1672141465.537564975] [find_wall_service]: STOPPING ROBOT...

I am not really sure what I am still doing wrong though. Do you think there is still a problem with my main() function ?

Thank you

Hi @e.na.hatem ,

Good! :+1:

No, you don’t have to use MultiThreadedExecutor. In fact, since the service is synchronous, the client can call the service only once. Only after the service finishes, another call from the client can be made.

Ok, now I understand what you have done. I did this rosject differently, so I got a little confused.

That is actually what I implemented when I did this project. I basically used the laser scan readings from the front of the robot to determine if the robot is facing an obstacle or a wall.

So now, coming to your code:
After you send a request to the FindWall Service, you must wait for the service to complete and return you the result - until then, your callback should not start.
The best way to check this is to implement a boolean flag that checks if the service is completed.
Something like (pseudocode):

call findwall service
if findwall.result = False:
    sleep(1.0)
else:
    start wall_following

Remember: Callbacks, as the name states, gets called back every time. So do not try to have a while loop inside callbacks. They will only block your code.

Nothing big, but you should not put wall_following.send_request() inside main(). This line should be in your motion() callback.

Also, you need to define ReentrantCallbackGroup() separately for each callback function.
So if you have, for example, 3 callbacks, then you will have:

self.group1 = ReentrantCallbackGroup()
self.group2 = ReentrantCallbackGroup()
self.group3 = ReentrantCallbackGroup()

You should not re-use the same callback group. This is a (beginner’s) mistake - even I did this mistake and learned from it.

Regards,
Girish

1 Like

Thank you for your explanation @girishkumar.kannan !
I made some progress with my code thanks to you. The request is now being sent to the service server from the callback function and the robot performs the wall finding procedure. However, there is still something I am doing wrong because the wall following procedure is not starting.

I was sending the request with the following line of code:

self.future = self.cli.call(self.req)

Then, I was checking the result to see if I should start the wall following procedure as follows:

if self.future.result() is True:
     self.wall_following()

However, I was getting an error stating that ‘future is not an attribute of the class’.

So, I defined the future variable in the class constructor as follows:

self.future = FindWall.Response()

And I am checking the for the server response as follows:

 if self.future.result() is False:
            self.get_logger().info('Service response not received yet')
            time.sleep(1)
        else:
            self.wall_following()

And, when I run the code now, I get the following error after 10-20 seconds of the robot being ready for the wall following procedure:

[wall_finding-1] [INFO] [1672474115.768644655] [find_wall_service]: The closest wall is currently on the right - Stopping robot...
[wall_finding-1] [INFO] [1672474115.770155218] [find_wall_service]: STOPPING ROBOT...
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
[wall_following_v2-2] The following exception was never retrieved: 'FindWall_Response' object has no attribute 'result'
^C[WARNING] [launch]: user interrupted with ctrl-c (SIGINT)

I should point out that I did not receive the message “Service response not received yet” that should be sent to the terminal in case the response of the service server is False.

I am not sure what I am doing wrong. Should I declare the future variable in the constructor as a different variable ? Or is my way of checking for the response of the service server incorrect ?

You can also find my modified code below:

import sys
import time
from threading import Thread

# import python ros client
import rclpy
# import the ROS2 python libraries
from rclpy.node import Node
# import Twist module from geometry_msgs.msg
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from custom_interface.srv import FindWall
from std_srvs.srv import Empty
# import Quality of Service library, to set the correct profile and reliability to read sensor data.
from rclpy.qos import ReliabilityPolicy, QoSProfile
from rclpy.callback_groups import ReentrantCallbackGroup, MutuallyExclusiveCallbackGroup
from rclpy.executors import MultiThreadedExecutor, SingleThreadedExecutor


class Wall_Following(Node):

    def __init__(self):
        # naming the class constructor (node name)
        super().__init__('wall_following')

        self.group1 = ReentrantCallbackGroup()
        self.group2 = ReentrantCallbackGroup()
        self.group3 = ReentrantCallbackGroup()

        # creating client for the /find_wall service server
        self.cli = self.create_client(
            FindWall, 'find_wall', callback_group=self.group1)
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = FindWall.Request()
        self.future = FindWall.Response()

        # creating the publisher object
        self.publisher_ = self.create_publisher(Twist, '/cmd_vel', 10)
        # creating the subscriber object
        self.subscriber_ = self.create_subscription(
            LaserScan,
            '/scan',
            self.read_measurement,
            # is the most used to read LaserScan data and some sensor data.
            QoSProfile(depth=10, reliability=ReliabilityPolicy.BEST_EFFORT),
            callback_group=self.group2
        )
        # define the timer period for 0.5 seconds
        self.timer_period = 0.5
        # declare variable to store all the laser messages
        self.laser_msg = LaserScan()
        # declare the variable to save the front laser scan reading
        self.forward_laser = 0.0
        # declare the variable to save the right laser scan reading
        self.right_laser = 0.0
        # declare the velocity twist message
        self.cmd = Twist()
        # create time
        self.timer = self.create_timer(
            self.timer_period, self.motion, callback_group=self.group3)
        # create Boolean for synchronous request
        self.request_sent = False

    def stop_robot(self):
        self.get_logger().info('STOPPING ROBOT...')
        self.cmd.linear.x = 0.0
        self.cmd.angular.z = 0.0
        self.publisher_.publish(self.cmd)

    def rotate_left(self):
        self.get_logger().info(
            f'Moving away from right wall - distance to right wall = {self.right_laser} m')
        self.cmd.linear.x = 0.1  # making sure that the robot is not moving forward
        self.cmd.angular.z = 0.2  # rotate counterclockwise to point to the closest wall
        self.publisher_.publish(self.cmd)

    def rotate_right(self):
        self.get_logger().info(
            f'Moving away from right wall - distance to right wall = {self.right_laser} m')
        self.cmd.linear.x = 0.1  # making sure that the robot is not moving forward
        self.cmd.angular.z = -0.2  # rotate counterclockwise to point to the closest wall
        self.publisher_.publish(self.cmd)

    def move_forward(self):
        self.get_logger().info('MOVING ROBOT FORWARD...')
        self.cmd.linear.x = 0.1
        self.cmd.angular.z = 0.0
        self.publisher_.publish(self.cmd)

    def hard_left_rotation(self):
        self.get_logger().info(
            f'Getting close to front wall - changing direction - distance to front wall = {self.forward_laser} m')
        self.cmd.linear.x = 0.05
        self.cmd.angular.z = 0.4
        self.publisher_.publish(self.cmd)

    def send_request(self):
        self.get_logger().info('SENDING REQUEST...')
        self.future = self.cli.call(self.req)

    def wall_following(self):

        self.get_logger().info('ENTERING WALL_FOLLOWING FUNCTION...')
        # check if the robot is far from the front wall
        if self.forward_laser > 0.5:
            # if the robot is far from the front wall and far from the right wall, move closer to the right wall
            if self.right_laser > 0.3:
                self.rotate_right()
            # if the robot is far from the front wall and close to the right wall, move away from the right wall
            elif self.right_laser < 0.3:
                self.rotate_left()
            # if the robot is far from the front wall is at a distance between 0.2 m and 0.3 m from the right wall, keep moving forward
            else:
                self.move_forward()
            # if the robot is close to a front wall, increase angular velocity while still going forward
        else:
            self.hard_left_rotation()

        # self.get_logger().info(
        #     f'Front laser measurement : {self.forward_laser} m')
        # self.get_logger().info(
        #     f'Right laser measurement : {self.right_laser} m')

    def motion(self):

        # sending synchronous request
        if self.request_sent is False:
            # making sure that the request will not be called again in the callback function
            self.request_sent = True
            self.send_request()

        if self.future.result() is False:
            self.get_logger().info('Service response not received yet')
            time.sleep(1)
        else:
            self.wall_following()

    def read_measurement(self, msg):
        self.laser_msg = msg
        self.forward_laser = self.laser_msg.ranges[359]
        self.right_laser = self.laser_msg.ranges[179]


# def main(args=None):
#     # initialize the ROS communication
#     rclpy.init(args=args)
#     # declare the node constructor
#     wall_following = Wall_Following()

#     spin_thread = Thread(target=rclpy.spin, args=(wall_following,))
#     spin_thread.start()

#     wall_following.send_request()

#     wall_following.destroy_node()
#     # shutdown the ROS communication
#     rclpy.shutdown()

def main(args=None):  # check the main function of a service node
    rclpy.init(args=args)
    try:
        wall_following = Wall_Following()
        executor = MultiThreadedExecutor(num_threads=4)
        executor.add_node(wall_following)
        try:
            executor.spin()
        finally:
            executor.shutdown()
            wall_following.destroy_node()

    finally:
        rclpy.shutdown()


if __name__ == '__main__':
    main()

Thanks again!

Hi @e.na.hatem ,

Since you have already declared the service response with the following line:

self.future = FindWall.Response()

This is correct, by the way!

All you have to do to get the result is (in your case):

# self.future.result() will not work !!!
result = self.future.wallfound # this is either True or False

So the way you would check is also simple:

# if self.future.result() is False: # delete this line
if not self.future.wallfound: # use this line instead
    self.get_logger().info('Service response not received yet')
    time.sleep(1)
else:
    self.wall_following()

I hope this gets your problem fixed.

Let me know if you still have issues.

Regards,
Girish

PS: When your error says that 'FindWall_Response' object has no attribute 'result' - it literally means that your object has no such variable/attribute !

Some Additional Advice:

  1. You do not have to name the variables exactly as specified in tutorial notes. You can use self.result instead of self.future for your service result.
  2. You do not have to copy-paste my example lines “as-is”. You can rename self.group1 as self.client_cbg (cbg is short for call back group). self.group2 as self.scan_sub_cbg and self.group3 as self.timer_cbg
  3. Also you do not have to declare the groups with name. You can directly do self.cli = self.create_client(FindWall, 'find_wall', callback_group=ReentrantCallbackGroup())
  4. You can extend your main() function’s exception handling block to a try-except-finally from just try-finally - this will help you print something in case you have an exception in the try block.

That’s all for now! :smile:

1 Like

Thank you so much for your help @girishkumar.kannan the code finally works now!
I just need some clarification for when we should set each callback function in a node to a different ReentrantCallbackGroup(). Is it best practice to always use
the MultiThreadedExecutor ?

Thank you

Hi @e.na.hatem ,

Glad you got it working!

Refer this link: Using Callback Groups — ROS 2 Documentation: Foxy documentation

You have two callback group types:

  1. MutuallyExclusiveCallbackGroup: More like serialized (non-parallel, a.k.a, sequential), that gets executed one after another and not simultaneously.
    Use this if you have callbacks that need not be run together, that is, when your main program is not dependent on more than one callback at any point of time.
    A (valid) example: a service callback followed by a odometry callback.
  2. ReentrantCallbackGroup: This is parallel (non-sequential), that gets executed together (although it gets polled since there is no true parallelism in computation [I believe]).
    Use this if you have callbacks that needs to be run together, that is, when your main program is dependent on more than one callback at any point of time.
    A (valid) example: a laser scan callback and odometry callback.

I believe, each of the above two groups is a global group, more like thread containers. So when you allocate one callback from one or any node, they get added into that global set of callbacks and gets called back when required.
Therefore, you specify one callback group type for each callback you define in your program.
[This is what I believe, although I may be wrong or not exactly right.]

Not exactly. If you have just one timer callback (for publisher) and one subscriber callback where the rate of publishing is not too fast and the rate of subscription is also not too fast, you can work without MultiThreadedExecutor. But if you do not use MultiThreadedExecutor, your program chooses SingleThreadedExecutor by default, even if you do not explicitly specify SingleThreadedExecutor anywhere in your code.

But, when you have more than 2 callbacks, specifically, when you want your callbacks to run in parallel, then the only best option is to use MultiThreadedExecutor. [Again, this is what I believe, I may be wrong.]

I hope I clarified your doubts.

Let me know if you are still unclear of something.

Regards,
Girish

1 Like

Thank you so much for your help @girishkumar.kannan !

1 Like