Hi,
I am working on the Services Part of the Wall Follower project. Myproblem is that the send_request() function never finishes. The service client calls the server perfectly, and the robot moves as expected. But, as the send_request() function does not finish, the wall_follower_node does not start.
First, I was trying to launch the Syncronous client from the main function, as the tutorial shows. I got the problem mentioned. Then, I read the following topic:
I tried to implement the service client as in the topic, but I got the same result. I will appreciate your help.
My wall_follower_node code is the following:
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from nav_msgs.msg import Odometry
from rclpy.qos import ReliabilityPolicy, QoSProfile
from custom_interfaces.srv import FindWall
from threading import Thread
import numpy as np
import time
class WallFollower(Node):
def __init__(self):
super().__init__('wall_follower')
# Wall finder client
self.wall_finder_client = self.create_client(FindWall, 'find_wall')
# checks once per second if a Service matching the type and name of the Client is available.
while not self.wall_finder_client.wait_for_service(timeout_sec=1.0):
# if it is not available, a message is displayed
self.get_logger().info('service not available, waiting again...')
self.req = FindWall.Request()
self.response = FindWall.Response()
self.request_sent = False
# Wall follower functionality
self.cmd_vel_publisher_ = self.create_publisher(Twist, 'cmd_vel', 10)
self.laser_subscriber_ = self.create_subscription(
LaserScan,
'/scan',
self.laser_callback,
QoSProfile(depth=10, reliability=ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE)) # is the most used to read LaserScan data and some sensor data.
self.odom_subscriber_ = self.create_subscription(
Odometry,
'/odom',
self.odom_callback,
QoSProfile(depth=10, reliability=ReliabilityPolicy.RMW_QOS_POLICY_RELIABILITY_RELIABLE)) # is the most used to read LaserScan data and some sensor data.
self.timer_period = 0.5
self.publisher_timer = self.create_timer(self.timer_period, self.motion)
self.distance_to_wall = 0.0
self.rotation_z = 0.0
self.distance_to_front_wall = 0.0
self.linear_vel = 0.15
self.angular_vel = 0.1
self.shift_angle = 0.0
self.angle_tol = 0.05 * np.pi / 180
self.print_timer_period = 2.5
self.print_timer = self.create_timer(self.print_timer_period, self.print_function)
self.dist_tol = 0.005
def send_request(self):
# send the request
self.get_logger().info('Service has started')
self.response = self.wall_finder_client.call(self.req)
# self.get_logger().info('SERVICE HAS FINISHED')
def print_function(self):
self.get_logger().info('Distance to wall: "%s"' % str(self.distance_to_wall))
self.get_logger().info('Distance to front wall: "%s"' % str(self.distance_to_front_wall))
self.get_logger().info('Angle direccion: "%s"' % str(self.rotation_z * 180/ np.pi))
self.get_logger().info('Shift angle: "%s"' % str(self.shift_angle * 180/ np.pi))
def motion_to_center(self):
angular_z = 0.0
delta_dist_to_wall = self.distance_to_wall - 0.25
if delta_dist_to_wall < 0:
turn_sign = +1
else:
turn_sign = -1
if abs(delta_dist_to_wall) >= self.dist_tol:
dist_to_go = abs(delta_dist_to_wall) / np.sin(abs(self.shift_angle))
if dist_to_go / (self.timer_period * self.linear_vel) >= 1:
linear_x = self.linear_vel
angular_z = turn_sign * self.angular_vel
else:
linear_x = (dist_to_go / (self.timer_period * self.linear_vel))* self.linear_vel
angular_z = (-1) * self.shift_angle / self.timer_period
else:
linear_x = self.linear_vel
if self.shift_angle >= self.angle_tol:
angular_z = (-1) * self.shift_angle / self.timer_period
return (linear_x, angular_z)
def motion(self):
# print the data
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()
self.get_logger().info('SERVICE HAS FINISHED')
if not self.response.wallfound:
self.get_logger().info('Service response not received yet')
else:
msg = Twist()
if self.distance_to_wall > 0.3:
msg.linear.x = self.linear_vel
msg.angular.z = - self.angular_vel
msg_to_print = "The robot is approaching the wall"
elif self.distance_to_wall < 0.2:
msg.linear.x = self.linear_vel
msg.angular.z = self.angular_vel
msg_to_print = "The robot is moving away to the wall"
else:
linear_x, angular_z = self.motion_to_center()
msg.linear.x = linear_x
msg.angular.z = angular_z
msg_to_print = "The robot is advancing forward"
self.get_logger().info(msg_to_print)
if self.distance_to_front_wall < 0.5:
msg.linear.x = self.linear_vel
msg.angular.z = self.angular_vel * 1.5
self.get_logger().info("The robot is turning left to avoid front wall")
self.cmd_vel_publisher_.publish(msg)
# Logic of move
def laser_callback(self, msg):
# print the log info in the terminal
shift_angle = self.rotation_z % (np.pi/2)
# self.get_logger().info('Shift angle 1: "%s"' % str(shift_angle* 180/ np.pi))
if shift_angle >= np.pi/4 and shift_angle < np.pi/2:
shift_angle -= (np.pi/2)
self.shift_angle = shift_angle
# self.get_logger().info('Shift angle 2: "%s"' % str(shift_angle* 180/ np.pi))
range_angle = (3 * np.pi / 2) - shift_angle - np.pi
# self.get_logger().info('Range angle: "%s"' % str(range_angle* 180/ np.pi))
ranges_length = round((2 * np.pi / msg.angle_increment) + 1)
# self.get_logger().info('Ranges length: "%s"' % str(ranges_length))
range_idx = int(range_angle * ranges_length / (2 * np.pi))
# self.get_logger().info('Ranges index: "%s"' % str(range_idx))
self.distance_to_wall = msg.ranges[range_idx]
# self.get_logger().info('Distance to wall: "%s"' % str(self.distance_to_wall))
self.distance_to_front_wall = msg.ranges[360]
def odom_callback(self, msg):
# print the log info in the terminal
x = msg.pose.pose.orientation.x
y = msg.pose.pose.orientation.x
z = msg.pose.pose.orientation.z
w = msg.pose.pose.orientation.w
_, _, yaw = self.euler_from_quaternion([x, y, z, w])
# self.get_logger().info('Yaw angle: "%s"' % str(yaw * 180/ np.pi))
rotation_z = yaw % np.pi
if yaw < 0:
rotation_z += np.pi
self.rotation_z = rotation_z
# self.get_logger().info('Angle direccion: "%s"' % str(rotation_z * 180/ np.pi))
def euler_from_quaternion(self, quaternion):
"""
Converts quaternion (w in last place) to euler roll, pitch, yaw
quaternion = [x, y, z, w]
Below should be replaced when porting for ROS2 Python tf_conversions is done.
"""
x = quaternion[0]
y = quaternion[1]
z = quaternion[2]
w = quaternion[3]
sinr_cosp = 2 * (w * x + y * z)
cosr_cosp = 1 - 2 * (x * x + y * y)
roll = np.arctan2(sinr_cosp, cosr_cosp)
sinp = 2 * (w * y - z * x)
pitch = np.arcsin(sinp)
siny_cosp = 2 * (w * z + x * y)
cosy_cosp = 1 - 2 * (y * y + z * z)
yaw = np.arctan2(siny_cosp, cosy_cosp)
return roll, pitch, yaw
def main(args=None):
# rclpy.init(args=args)
# # declare the node constructor
# client_node = WallFollower()
# # run the send_request() method
# client_node.send_request()
# spin_thread = Thread(target=rclpy.spin, args=(client_node,))
# spin_thread.start()
# response = client_node.send_request()
# client_node.get_logger().info( 'Wall finder service has finished')
# client_node.destroy_node()
# rclpy.shutdown()
rclpy.init(args=args)
wall_follower_node = WallFollower()
rclpy.spin(wall_follower_node)
wall_follower_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Kind regards,
Ronaldo