How to use a custom interface with an empty request

Good afternoon,

I am working on the ros project part 2, and my server does not start.
I have in my customer shell :“waiting for service to become available”, and nothing happends.

Here is my server

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from findwall.srv import Findwall


class Service(Node):
    def __init__(self):
        # Define the class constructor intialize the node
        super().__init__('find_wall')
        # Define the server object
        self.service = self.create_service(
            Findwall, 'findwall', self.find_wall_callback)
        # Define the publisher object
        self.publisher = self.create_publisher(Twist, 'cmd_vel', 10)
        # Define the subscriber object
        self.subscriber = self.create_subscription(
            LaserScan, 'scan', self.laser_callback, 10)

        # Initialisation of the laser_270_degree and laser_360_degree values at 0
        self.laser_270_degree = 0
        self.laser_360_degree = 0

        # Initialisation of the variable that will take the Twist message
        self.move = Twist()

    def laser_callback(self, sensor_msgs):
        self.laser_270_degree = sensor_msgs.ranges[270]
        self.laser_360_degree = sensor_msgs.ranges[360]

    def find_wall_callback(self, request, response):
        # The callback function receives the self-class parameter,
        # received along with two parameters called request and response
        # - receive the data by request
        # - return a result as a response

        # Print the degug function at the info level the information related to the laser
        self.get_logger().info('I receive on the right side: "%s"' %
                               str(self.laser_360_degree))

        # Stop the robot
        self.move.linear.x = 0.0

        # Check if a wall is around
        if(str(self.laser_360_degree) == 'inf'):
            # Robot turns to find a wall
            self.move.angular.z = 0.3
        else:
            # Stop turning because wall found
            self.move.angular.z = 0.0

            # Check the distance between the wall and the robot
            if(self.laser_360_degree != 0.3):
                # Robot moves
                self.move.linear.x = 0.5
            else:
                # Robot stops
                self.move.linear.x = 0.0

                # Rotate the robot to be parallel to the wall
                if(self.laser_270_degree != 0.3):
                    # Rotate the robot
                    self.move.angular.z = 0.3
                else:
                    # Stop the rotation of the robot
                    self.move.angular.z = 0.0
                    # Return an answer
                    response.wallfound = True

        # Publish the robot pose in the pose topic /cmd_vel
        self.publisher.publish(self.move)

        return response


def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    # declare the node constructor
    Find_wall_move = Service()
    # pause the program execution, waits for a request to kill the node (ctrl+c)
    rclpy.spin(Find_wall_move)
    # shutdown the ROS communication
    rclpy.shutdown()


if __name__ == '__main__':
    main()

And I call the server with:
ros2 service call /find_wall std_srvs/Empty ‘{}’

Can you explain to me why it does not work?

Regards

Hi @ClairePottier ,

I took the liberty to fix your issue title and category. Also added some tags.

That is the wrong format. You should instead use:

ros2 service call /find_wall custom_interfaces/srv/FindWall "{}"

After you have made the custom interface package, you do not use std_srvs anymore.
You can make use of the autocomplete feature in the command line.
After typing ros2 service call /find_wall, hit [TAB] key twice. It will give you options to choose from.
there you can choose the interface type and then hit [TAB] twice again to give you message structure options. Then you can send the message to your service server.

To check if find_wall service server is running:

user:~$ ros2 service list | grep find_wall
/find_wall
/find_wall_service_server_node/describe_parameters
/find_wall_service_server_node/get_parameter_types
/find_wall_service_server_node/get_parameters
/find_wall_service_server_node/list_parameters
/find_wall_service_server_node/set_parameters
/find_wall_service_server_node/set_parameters_atomicall

To call the service:

user:~$ ros2 service call   # hit TAB key twice here to list all services
--rate                                                    /find_wall_service_server_node/set_parameters_atomically
-r                                                        /ros_bridge/describe_parameters
/find_wall                                                /ros_bridge/get_parameter_types
/find_wall_service_server_node/describe_parameters        /ros_bridge/get_parameters
/find_wall_service_server_node/get_parameter_types        /ros_bridge/list_parameters
/find_wall_service_server_node/get_parameters             /ros_bridge/set_parameters
/find_wall_service_server_node/list_parameters            /ros_bridge/set_parameters_atomically
/find_wall_service_server_node/set_parameters

user:~$ ros2 service call /find_wall   # hit TAB key twice here to list options
--rate                          -r                              custom_interfaces/srv/FindWall

user:~$ ros2 service call /find_wall custom_interfaces/srv/FindWall   # hit TAB key twice here to list options
--rate  -r      {}\

user:~$ ros2 service call /find_wall custom_interfaces/srv/FindWall "{}"   # this will call the service

I took a quick glance into your code, I have a vague feeling that you will run into callback issues. Let me know if you face any errors.

I hope this clarifies your doubt(s). Let me know if you are still unclear.

Regards,
Girish

Hi @girishkumar.kannan

Thank you for your answer.
Yes, the ros project textbook/announcement does not give the correct command. This confused me and explains why I couldn’t find anything on the internet about it.

I will try to debug my code.
If I have any problems, I will contact you. Thanks for your help.

Regards,
Claire

Good afternoon

I changed my code, but it is exercute only one time when I call it with the ros2 service call.
I would like to know how to call it more than one time? I though about a while loop but the laser reading is not update.

How can I have access to the right laser scan values?

Thanks for your help.
Claire

Here is my code

import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from findwall.srv import Findwall


class Service(Node):
    def __init__(self):
        # Define the class constructor intialize the node
        super().__init__('find_wall')
        # Define the server object
        self.service = self.create_service(
            Findwall, 'findwall', self.find_wall_callback)
        # Define the publisher object
        self.publisher = self.create_publisher(Twist, 'cmd_vel', 10)
        # Define the subscriber object
        self.subscriber = self.create_subscription(
            LaserScan, 'scan', self.laser_callback, 10)

        # Initialisation of the laser_270_degree and laser_360_degree values at 0
        self.laser_270_degree = 0
        self.laser_360_degree = 0

        # Initialisation of the variable that will take the Twist message
        self.move = Twist()
        print(0)

    def laser_callback(self, sensor_msgs):
        self.laser_270_degree = sensor_msgs.ranges[270]
        self.laser_360_degree = sensor_msgs.ranges[359]
        print(1)

    def find_wall_callback(self, request, response):

        while response.wallfound == False:
            self.get_logger().info('I receive on the right side: "%s"' %
                                   str(self.laser_360_degree))

            # Check if a wall is around
            if(self.laser_360_degree >= 0.6):
                # Robot turns to find a wall
                self.move.angular.z = 0.3
            else:
                # Stop turning because wall found
                self.move.angular.z = 0.0

                if(self.laser_360_degree > 0.3):
                    # Robot moves
                    self.move.linear.x = 0.1
                else:
                    # Robot stops
                    self.move.linear.x = 0.0

                    # Rotate the robot to be parallel to the wall
                    if(self.laser_270_degree > 0.3):
                        # Rotate the robot
                        self.move.angular.z = 0.3
                    else:
                        # Stop the rotation of the robot
                        self.move.angular.z = 0.0
                        response.wallfound == True

            # Publish the robot pose in the pose topic /cmd_vel
            self.publisher.publish(self.move)

        return response


def main(args=None):
    # initialize the ROS communication
    rclpy.init(args=args)
    # declare the node constructor
    Find_wall_move = Service()
    # pause the program execution, waits for a request to kill the node (ctrl+c)
    rclpy.spin(Find_wall_move)
    # shutdown the ROS communication
    rclpy.shutdown()


if __name__ == '__main__':
    main()


Hi @ClairePottier ,

Exactly what I thought! I knew this would happen in your code.
That’s why I said this in my previous post:

The solution to your problem is quite simple.
You need to use MultiThreadedExecutor with ReentrantCallbackGroup for both your scan_callback and the service_callback.

Your main() function will now transform into this version:

def main(args=None):
    rclpy.init(args=args)
    try:
        service_server = FindWallServiceServer()
        executor = MultiThreadedExecutor(num_threads=4)
        executor.add_node(service_server)
        try:
            executor.spin()
        except:
            pass
        finally:
            executor.shutdown()
            service_server.destroy_node()
    except:
        pass
    finally:
        rclpy.shutdown()

Implement this concept in your code. This will solve your problem. Let me know if you face any problems.

Regards,
Girish

Hi @girishkumar.kannan

Thank you for your answer.
May I ask you why I need to use MultiThreaded here?
Even after reading the course about the server and the callback, I would never have thought of it.

My server code and the codes in the server unit are quite similar. Why in the server unit, threads are not used? Why here I need them?

Thanks for your answer.
Regards,
Claire

Hi @ClairePottier ,

I believe the concept of “Executors and Callbacks” comes after the Services chapter in the current course notes. So if you are strictly following the instructions in the course notes and working on Part 2 of your rosject, you may have not have much exposure to Executors and Callbacks.

So, according to the service callback design, you need laser scan callback to be running in parallel along with service callback.
In a program that does not explicitly state the use of MultiThreadedExecutor, SingleThreadedExecutor is used by default. SingleThreadedExecutor cannot efficiently handle parallel process callbacks. Therefore you need MultiThreadedExecutor.
You cannot use MutuallyExclusiveCallbackGroup either - because they execute callbacks in a sequential way, one after another. Therefore, you need ReentrantCallbackGroup to execute service callback and laser scan callback in parallel mode, not one after another, but together.

Also, if you do not implement MultiThreadedExecutor, your service will be called only once. You cannot call it again, even if the service server is online and running.

I hope this clarifies your doubt(s). Let me know if anything is still unclear.

Regards,
Girish

Hi @girishkumar.kannan

Thanks for this explanation. I understand better the concept of thread.

However, it is still not working. The server is still running only one time, and the server reads only one time the laserScan callback.

Here is my script

import rclpy
from rclpy.node import Node
from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.executors import MultiThreadedExecutor
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from findwall.srv import Findwall


class FindWallServiceServer(Node):
    def __init__(self):
        # Define the class constructor intialize the node
        super().__init__('find_wall')
        # Create 2 MutuallyExclusiveCallbackGroup
        self.group1 = ReentrantCallbackGroup()
        self.group2 = ReentrantCallbackGroup()

        # Define the server object
        self.service = self.create_service(
            Findwall, 'findwall', self.find_wall_callback, callback_group=self.group2)
        # Define the publisher object
        self.publisher = self.create_publisher(Twist, 'cmd_vel', 10)
        # Define the subscriber object
        self.subscriber = self.create_subscription(
            LaserScan, 'scan', self.laser_callback, 10, callback_group=self.group1)

        # Initialisation of the laser_270_degree and laser_360_degree values at 0
        self.laser_270_degree = 0
        self.laser_360_degree = 0

        # Initialisation of the variable that will take the Twist message
        self.move = Twist()
        print(0)

    def laser_callback(self, sensor_msgs):
        self.laser_270_degree = sensor_msgs.ranges[270]
        self.laser_360_degree = sensor_msgs.ranges[359]
        print(1)

    def find_wall_callback(self, request, response):
        self.get_logger().info('I receive on the right side: "%s"' %
                               str(self.laser_360_degree))

        # Check if a wall is around
        if(self.laser_360_degree >= 0.6):
            # Robot turns to find a wall
            self.move.angular.z = 0.3
        else:
            # Stop turning because wall found
            self.move.angular.z = 0.0
            print(1)

            if(self.laser_360_degree > 0.3):
                # Robot moves
                self.move.linear.x = 0.1
            else:
                # Robot stops
                self.move.linear.x = 0.0
                print(2)

                # Rotate the robot to be parallel to the wall
                if(self.laser_270_degree > 0.3):
                    # Rotate the robot
                    self.move.angular.z = 0.3
                else:
                    # Stop the rotation of the robot
                    self.move.angular.z = 0.0
                    response.wallfound == True
                    print(3)

        # Publish the robot pose in the pose topic /cmd_vel
        self.publisher.publish(self.move)

        return response


def main(args=None):
    rclpy.init(args=args)
    try:
        service_server = FindWallServiceServer()
        executor = MultiThreadedExecutor(num_threads=4)
        executor.add_node(service_server)
        try:
            executor.spin()
        except:
            pass
        finally:
            executor.shutdown()
            service_server.destroy_node()
    except:
        pass
    finally:
        rclpy.shutdown()


if __name__ == '__main__':
    main()

Thanks for your help.
Regards,
Claire

Hi @ClairePottier ,

I do not exactly know how your code works. Could you perhaps copy-paste your terminal output as a code-block?

  1. Post the output of your service server from one terminal after the service call
  2. Post the output of your service call / client from another terminal, after calling the service.

Regards,
Girish

Hi @girishkumar.kannan

Yes sure.
My service call output:

user:~/ros2_ws$ ros2 service call /findwall findwall/srv/Findwall "{}"
requester: making request: findwall.srv.Findwall_Request()

response:
findwall.srv.Findwall_Response(wallfound=False)

My service server output:

user:~/ros2_ws$ ros2 launch wall_follower start_wall_finder.launch.py
[INFO] [launch]: All log files can be found below /home/user/.ros/log/2023-03-24-15-14-20-021416-3_xterm-11152
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [wall_finder-1]: process started with pid [11202]
[wall_finder-1] [INFO] [1679670865.754321753] [find_wall]: Ireceive on the right side: "0.3463899791240692"
^C[WARNING] [launch]: user interrupted with ctrl-c (SIGINT)
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 1
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0
[wall_finder-1] 0

The robot moves but does not stop due to the non update of the laserScan reading

Hi @ClairePottier ,

I think I can see why your code is not working.

The service callback will be executed only once when the client calls this service. Unlike scan callback, this service callback will not execute multiple times.

I think the code you have provided inside the service callback executes once and exits. You can actually add loops inside your service callback. It will not cause any problem - since service callback executes only once after being called.

Also from your output, I see a lot of 0’s but alone one single 1.

And you have print(1) in your code inside scan callback as well as in your service callback.

I am not sure which function printed the 1 in the output. I am guessing it is the service call.

Try to make more debug print statement to identify what is happening. I think the scan callback is not executing. Comment out the service declaration and service callback. Check if laser subscriber callback is functioning alone by itself.
Then add the service callback and check your output.

Remember to compile your package every time before execution. In ROS2, you must compile after any change you make in python code also.

Let me know, if your problem still occurs.

Regards,
Girish

Hi @girishkumar.kannan

Thank you for your answer.
I tried to add a loop in my service callback with the lasercallback but it failed.
How can I call my laser callback in the loop of my service callback?

Regards,
Claire

Hi @ClairePottier ,

I did not tell you to add the laser callback into your service callback. I think you have mistaken me.

I told you to check if the callbacks are working properly, individually by themselves.

  1. Comment out the service declaration and service callback. Now execute your program and check if your scan callback works.
  2. Now, comment out the laser callback and create a dummy service callback with some print messages. Execute your program again to check if your service callback works.
  3. Finally merge both the callbacks once you confirm that both callbacks are working properly. Execute and check if the program works with both the callbacks.

Now modify your code to use the laser scan readings in your service callback to make the robot find a wall.

I hope my explanation is clear.

Regards,
Girish

Hi @girishkumar.kannan

Thank you for your answer. I don’t think I understood the reason for a server.
Can you explain to me how a server works?
Will my server code be executed only once before a single request is sent and received?
Or will my server code be executed in the same way as a topic code?

Kind regards,
Claire

Hi @ClairePottier ,

That is actually explained in the course notes itself. Under the “Services” and “Actions” chapters.
Services and Actions are concepts of communication in ROS just like Topics.

You can think of servers as programs that will help the robot do a special / separate task that the robot does not frequently use in its main program.

Let’s assume that you have a humanoid robot. Among all the different tasks it can do, let’s say one of it’s tasks is to pick up any trash from the floor and drop it into the nearby trashcan.
The main program of the robot would be obstacle avoidance and room exploration. As the robot explores the room, when suddenly it comes in contact with a trash - say snacks wrapper - the robot must pick it up and drop it in the nearest trashcan. So as the robot explores the room and when it finds the trash, it will call the service that would help the robot pick up the trash. Then there would be an action call that would enable the robot to drop the trash into the trashcan and return back to its original position, where it originally picked up the trash.
Again, the above is just an example. The uses for services and actions are plenty.

Yes, you cannot make multiple calls to the same server from different nodes / programs. That will not work. However, you can call a service multiple times from the same or different node, once the service completes.

Topics are more like data pipelines. You can use many subscribers to a single publisher - or - multiple publishers to a single subscriber. Topics do not have any usage limits, other than the possibility of the data throughput becoming slower if more connections are made to the same topic.
Services and Actions only work for a short period - only when needed by the main program.

I hope this explanation is clear. Let me know if anything is still unclear.

Regards,
Girish

Hi @girishkumar.kannan

Thank you for your explanation.
It makes more sense for me.

Kind regards,
Claire

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.