How to give message from a subscriber to a service server?

Hi there,

I am doing the first rosject with service. We need to rotate the robot to the correct direction according to laser data from “ranges”. But the rotation, I think, should be done in Service Server, and the corresponding laser data is from subscriber with topic “/scan”. So this is the question: how can I give the information from a subsciber into a service server?
As what I have learned in the course, we can only process information from “msg” of a subscriber when the “callback()” is called. There is no specific return to individually offer the “msg” to other usage. So I decide to use “class”. Inside a “class” I can save the “msg” from a subscriber into an attribute of class. But I still meet with problems. Here is my code:

#! /usr/bin/env python
import rospy
from find_wall_pkg.srv import FindWall, FindWallResponse
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
import numpy as np
import time

class find_wall_service_server_class():

def __init__(self):

    self.vel = Twist()

def callback(self, msg):
    self.laser_data = msg.ranges

def my_callback(self, request):

    # get laser data
    self.my_sub = rospy.Subscriber("/scan", LaserScan, self.callback)

    # 1 find the direction with shortest distance
    min_idx = np.argmin(self.laser_data)
    # define the angle need to be rotated
    angle = min_idx - 360

    # 2 rotation
    if angle > 0:
        # the ranges divided one round with 720 angular grids, each grid is 0.00872
        self.vel.angular.z = 0.00872 * angle
        self.my_pub.publish(self.vel)
        # rotate 1 second
        time.sleep(1)
    elif angle <= 0:
        self.vel.angular.z = 0.00872 * angle
        self.my_pub.publish(self.vel)
        time.sleep(1)
    self.vel.linear.z = 0
    self.my_pub.publish(self.vel)

    # 3 move forward to the wall
    self.my_sub = rospy.Subscriber("/scan", LaserScan, self.callback)
    while self.laser_data[360] > 0.3:
        self.vel.linear.x = 0.1
        self.my_pub.publish(self.vel)
        time.sleep(1)
        self.my_sub = rospy.Subscriber("/scan", LaserScan, self.callback)
    self.vel.linear.x = 0
    self.my_pub.publish(self.vel)

    # 4 rotate to be along with the wall
    # roatate 90 degree anticlockwise, making the wall on the right side of the bot
    self.vel.angular.z = 0.00872 * 90
    self.my_pub.publish(self.vel)
    time.sleep(1)
    self.vel.linear.z = 0
    self.my_pub.publish(self.vel)

    # 5 return message
    my_response = FindWallResponse()
    my_response.wallfound = True
    return my_response

def main(self):

    rospy.init_node("find_wall_service_server_node")
    self.rate = rospy.Rate(1)
    self.my_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=10)
    self.my_Service = rospy.Service(
        "/find_wall", FindWall, self.my_callback)
    rospy.loginfo("The service: /find_wall is ready")
    rospy.spin()

if name == ‘main’:
find_wall_instance = find_wall_service_server_class()
find_wall_instance.main()

The service node can be successfully launched. But when I call the service, a.k.a type in “rosservice call find_wall”, it gives error:

ERROR: service [/find_wall] responded with an error: b"error processing request: ‘find_wall_service_server_class’ object has no attribute ‘laser_data’"

[ERROR] [1663856409.121108, 5435.988000]: Error processing request: ‘find_wall_service_server_class’ object has no attribute ‘laser_data’
[‘Traceback (most recent call last):\n’, ’ File “/opt/ros/noetic/lib/python3/dist-packages/rospy/impl/tcpros_service.py”, line 632, in _handle_request\n response = convert_return_to_response(self.handler(request), self.response_class)\n’, ’ File “/home/user/catkin_ws/src/find_wall_pkg/scripts/find_wall_service_server.py”, line 25, in my_callback\n min_idx = np.argmin(self.laser_data)\n’, “AttributeError: ‘find_wall_service_server_class’ object has no attribute ‘laser_data’\n”]

Can anyone help me, what is going on here?

Please properly format the post by enclosing all the code between triple " ` ". It makes helping easier :grin:

1 Like

What you want to do is to declare your publisher & subscriber in the __init__ function. Every time a message is received the callback function will be called; that is when you want to interpret the data and then publish it in the same callback function.

Here’s the pseudocode-ish structre of the code:

import section

class CorrectDirection(Node):
    def __init__(self):
         define node
         create publisher
         create subscriber

    def subscriber_callback(self, msg):
        receive data
        figure our what action do you want the robot to take
        publish that action

def main():
    initialise and spin code

if __name__ == 'main':
    main()



@GasPatxo ,

I think @MeineLiebeAxt is using ROS 1. You have provided code for ROS 2.

– Girish

1 Like

But how to get the “msg” from subscriber in “my_callback()” of a service server?
Or may I ask you how do you do this task? I am still not sure if this gonna work with “class”.

Probably because you are creating the Subscriber inside of the service callback function (multiple times, actually). You should create the subscriber once in __init__() or main(). I modified a bit your code:

#! /usr/bin/env python
import rospy
from find_wall_pkg.srv import FindWall, FindWallResponse
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
import numpy as np
import time

class find_wall_service_server_class():
    def __init__(self):
        self.vel = Twist()
        
    def callback(self, msg):
        self.laser_data = msg.ranges

    def my_callback(self, request):
        # 1 find the direction with shortest distance
        min_idx = np.argmin(self.laser_data)
        # define the angle need to be rotated
        angle = min_idx - 360

        # 2 rotation
        if angle > 0:
            # the ranges divided one round with 720 angular grids, each grid is 0.00872
            self.vel.angular.z = 0.00872 * angle
            self.my_pub.publish(self.vel)
            # rotate 1 second
            time.sleep(1)
        elif angle <= 0:
            self.vel.angular.z = 0.00872 * angle
            self.my_pub.publish(self.vel)
            time.sleep(1)
        self.vel.linear.z = 0
        self.my_pub.publish(self.vel)

        # 3 move forward to the wall
        self.my_sub = rospy.Subscriber("/scan", LaserScan, self.callback)
        while self.laser_data[360] > 0.3:
            self.vel.linear.x = 0.1
            self.my_pub.publish(self.vel)
            time.sleep(1)
            self.my_sub = rospy.Subscriber("/scan", LaserScan, self.callback)
        self.vel.linear.x = 0
        self.my_pub.publish(self.vel)

        # 4 rotate to be along with the wall
        # roatate 90 degree anticlockwise, making the wall on the right side of the bot
        self.vel.angular.z = 0.00872 * 90
        self.my_pub.publish(self.vel)
        time.sleep(1)
        self.vel.linear.z = 0
        self.my_pub.publish(self.vel)

        # 5 return message
        my_response = FindWallResponse()
        my_response.wallfound = True
        return my_response

    def main(self):
        rospy.init_node("find_wall_service_server_node")
        self.rate = rospy.Rate(1) # This rate is not being used !
        self.my_sub = rospy.Subscriber("/scan", LaserScan, self.callback)
        self.my_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=10)
        self.my_Service = rospy.Service(
            "/find_wall", FindWall, self.my_callback)
        rospy.loginfo("The service: /find_wall is ready")
        rospy.spin()

if __name__ == 'main':
    find_wall_instance = find_wall_service_server_class()
    find_wall_instance.main()

These are the changes I made:


Yes, I am using ROS1. Would you please share the solution of this task, if you also has done that? I can only come up with the idea by “class”, but not really clear how to use that. Maybe “class” makes it more complex, is there a easier way?

I am sorry it doesn’t work. Actually this time, the service server directly finish the process, nothing happens…

Try this:

#! /usr/bin/env python
import rospy
from find_wall_pkg.srv import FindWall, FindWallResponse
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
import numpy as np
import time

class FindWallService():
    def __init__(self):
        self.vel = Twist()
        self.laser_data = None
        self.my_sub = rospy.Subscriber("/scan", LaserScan, self.sub_callback)
        self.my_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=10)
        self.my_Service = rospy.Service(
            "/find_wall", FindWall, self.server_callback)
        rospy.loginfo("The service: /find_wall is ready")
        
    def sub_callback(self, msg):
        self.laser_data = msg.ranges

    def server_callback(self, request):
        my_response = FindWallResponse()

        if self.laser_data == None:
            rospy.loginfo("No data received from subscriber")
            my_response.wallfound = False
            return my_response

        # 1 find the direction with shortest distance
        min_idx = np.argmin(self.laser_data)
        # define the angle need to be rotated
        angle = min_idx - 360

        # 2 rotation
        if angle > 0:
            # the ranges divided one round with 720 angular grids, each grid is 0.00872
            self.vel.angular.z = 0.00872 * angle
            self.my_pub.publish(self.vel)
            # rotate 1 second
            time.sleep(1)
        elif angle <= 0:
            self.vel.angular.z = 0.00872 * angle
            self.my_pub.publish(self.vel)
            time.sleep(1)
        self.vel.linear.z = 0
        self.my_pub.publish(self.vel)

        # 3 move forward to the wall
        while self.laser_data[360] > 0.3:
            self.vel.linear.x = 0.1
            self.my_pub.publish(self.vel)
            time.sleep(1)
        self.vel.linear.x = 0
        self.my_pub.publish(self.vel)

        # 4 rotate to be along with the wall
        # roatate 90 degree anticlockwise, making the wall on the right side of the bot
        self.vel.angular.z = 0.00872 * 90
        self.my_pub.publish(self.vel)
        time.sleep(1)
        self.vel.linear.z = 0
        self.my_pub.publish(self.vel)

        # 5 return message
        my_response.wallfound = True
        return my_response

def main():
    rospy.init_node("find_wall_service_server_node")
    FindWallService()
    rospy.spin()

if __name__ == 'main':
    main()
1 Like

still doesn’t work. Even the loginfo “The service: /find_wall is ready” not shows up, and the process directly finishes…

Yeah, sorry about that, I am mixing a bit ROS 1&2. Probably failed because I was doing nonsense in main(). I checked the ROS basics notebook and update the previous post’s code. Please try again, at least it should print "The service: /find_wall is ready"

@MeineLiebeAxt ,

Adding to @GasPatxo code edits, you will also not need rospy.spin() in a Service Server code. This is because the service callback is entered just once. If you use rospy.spin(), your program sometimes will not terminate after service has been completed.

– Girish

Unfortunately, it is still the same. Direct finished withou printing “The service: /find_wall is ready”.
Maybe “class” is not suitable for this task? Do you have a successful version?

The main problem here is that, I don’t know how to extract the information of subscriber (in subscriber’s callback()) into service server’s callback(). In order to achieve that, I choose “class”. But maybe my basic thought is wrong?

Is it possible that the callback() of subscriber to return something, and save that with an extra variable and then as input give it into callback() of service server?

Hi @MeineLiebeAxt ,

Classes / Classing is the best and easiest method to get this working. Use of no classes / global variables would be a nightmare.

No. Subscriber callbacks do not return anything. Subscriber callbacks should not return anything.

Yes, I have successful versions but cannot share them with you. It is like providing answer without struggle / learning.

@GasPatxo 's code should definitely work for you. Else there is some ROS environment setup issue that you have not noticed.

That is all there is to it.

Hope you find this helpful.

Regards,
Girish

No, your best bet is using the class-based approach @GasPatxo @girishkumar.kannan has been trying to show you.

Whatever information you need from the subscriber callback can be stored in an instance variable in the subscriber callback (which should be an instance method) and will also be accessible in the service server (which should also be an instance method.

You can take our free Python 3 for Robotics course to understand Python more.