0
$\begingroup$

Rosanswers logo

SOLVED See Solution at the end of post

Problem: Hey there,

I'm currently working on a project about navigating a robot to a goal point.

I already wrote some code to tackle this task. The code uses the gazebo/modelstates topic to get the position and orientation of the robot. The angle_to_goal is calculated by looking at the currentPosition and the goalPosition.

The robot should turn, until the difference between the yaw of the robot and the angle_to_goal is 0 +/- a threshold of 0.1. If this is the case, the robot should drive towards the goal.

This code is working pretty well in some cases where the robot gets into the acceptable range well and drives straight without any correction needed.

In other cases the robot will turn, then drive a very small distance, then correct its course again a little bit and repeat this which leads to a very slow and jittery path. I can imagine that the problem is that the robot gets into the acceptable range of the angle at the very border of it, then starts driving which makes the difference of the angles grow and leads the robot to turn the robot again but not enough so that it drives relatively fluently afterwards. Another case is that the robot seems to overshoot the desired angle_range or immediately leave it and doesn't stop turning.

In both cases the robot does not really find its way but keeps being stuck, misses the right angle and continues in this sub-optimal behavior. I already tried different angular-velocities and also other thresholds.

As I am a total beginner I don't know if my guess of the problem could be right. I don't know how i could do to solve the problem or at least improve the rate of the robot getting the right direction at the first time. Thank you in advance for any help!

Here is the code:

#! /usr/bin/env python
import rospy
from gazebo_msgs.msg import ModelStates
from tf.transformations import euler_from_quaternion
from geometry_msgs.msg import Twist
from geometry_msgs.msg import Point
from math import atan2

#start is x:0, y:0
x = 0.0
y = 0.0
theta = 0.0     #current angle of robot

#import ipdb; ipdb.set_trace()

def callback(msg):
    global x
    global y
    global theta

    x = msg.pose[1].position.x
    y = msg.pose[1].position.y
    rot_q = msg.pose[1].orientation
    (roll, pitch, theta) = euler_from_quaternion([rot_q.x, rot_q.y, rot_q.z, rot_q.w])


rospy.init_node ('subscriber')
sub = rospy.Subscriber('/gazebo/model_states', ModelStates, callback)
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)

speed = Twist()

r = rospy.Rate(4)

goal = Point()
goal.x = -2
goal.y = -1

while not rospy.is_shutdown():
    inc_x = goal.x - x                      #distance robot to goal in x
    inc_y = goal.y - y                      #distance robot to goal in y
    angle_to_goal = atan2 (inc_y, inc_x)    #calculate angle through distance from robot to goal in x and y
    print abs(angle_to_goal - theta)
    if abs(angle_to_goal - theta) > 0.1:    #0.1 because it too exact for a robot if both angles should be exactly 0
            speed.linear.x = 0.0
            speed.angular.z = 0.3
    else:
        speed.linear.x = 0.3                #drive towards goal
        speed.angular.z = 0.0

    pub.publish(speed)

 r.sleep()

Edit: Hey Delb,

thank you a lot for your answer!

It is not directly intended to just turn left but i thought that it would be easier to get started with only one direction and to add a logic about in which direction to turn later.

I will definitely try to implement your suggestion and see if it works.

SOLUTION: This is the code I ended up with, which seems to work pretty well.

#! /usr/bin/env python
import rospy
from gazebo_msgs.msg import ModelStates
from tf.transformations import euler_from_quaternion
from geometry_msgs.msg import Twist
from geometry_msgs.msg import Point
from math import atan2, sin, cos, pow, sqrt

#start is x:0, y:0
x = 0.0
y = 0.0
theta = 0.0     #current angle of robot

move_forward = False

def callback(msg):
    global x
    global y
    global theta

    x = msg.pose[1].position.x
    y = msg.pose[1].position.y
    rot_q = msg.pose[1].orientation
    (roll, pitch, theta) = euler_from_quaternion([rot_q.x, rot_q.y, rot_q.z, rot_q.w])


rospy.init_node ('subscriber')
sub = rospy.Subscriber('/gazebo/model_states', ModelStates, callback)
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)

speed = Twist()

r = rospy.Rate(4)

goal = Point()
goal.x = 2
goal.y = 2

while not rospy.is_shutdown():
    inc_x = goal.x - x                      #distance robot to goal in x
    inc_y = goal.y - y                      #distance robot to goal in y
    angle_to_goal = atan2 (inc_y, inc_x)    #calculate angle through distance from robot to goal in x and y
    dist = sqrt(pow(inc_x, 2) + pow(inc_y, 2))      #calculate distance

    #find out which turndirection is better
    # the bigger the angle, the bigger turn, - when clockwise
    turn = atan2(sin(angle_to_goal-theta), cos(angle_to_goal-theta))

    if abs(angle_to_goal - theta) < 0.1:    #0.1 because it too exact for a robot if both angles should be exactly 0
        move_forward = True

    speed.angular.z = 0.2 * turn

    if move_forward == True:
        #keep speed between 0.3 and 0.7
        if 0.1 * dist > 0.3 and 0.1 * dist < 0.7:
            speed.linear.x = 0.05 * dist
        elif 0.1 * dist > 0.7:
            speed.linear.x = 0.7
        else:
            speed.linear.x = 0.3


    pub.publish(speed)
    r.sleep()

Originally posted by SpaceTime on ROS Answers with karma: 50 on 2019-01-21

Post score: 0

$\endgroup$

1 Answer 1

0
$\begingroup$

Rosanswers logo

First note here : is it intended for your robot to be only turning left ? That could be problematic if the robot deviates too much at the left of the goal, it would be turning for almost 2*pi instead of a little bit to the right to correct its orientation.

Anyway, you should be able to achieve what you want with some little changes :

  1. I would directly set the angular speed equal to the angle difference. That would allow the robot to always correct its orientation when reaching the goal. So your angular speed at any time could be :

    speed.angular.z = angle_to_goal - theta
    

That being said you might want to have a function calculating the shortest angle (because your calculation might return 3*pi/2 instead of -pi/2) not to be always turning too much and also to deal with the case when your robot is aligned to the goal but the calculation returns 2*pi. If it's not what you want I would still recommend to get the sign of the angular difference to allow your robot to turn left or right when needed.

  1. I would change the condition. Instead of choosing between moving forward and turning, you could have a flag turning to true the first time your robot gets aligned to the goal and then allow it to move forward :

    move_forward = False        #define this outside of the while loop
    ...
    if abs(angle_to_goal - theta) > 0.1: 
            move_forward = True
    
    speed.angular.z = angle_to_goal - theta   #the angular speed is always the angualr difference
    if move_forward is True:
            speed.linear = 0.3                #the linear speed is only set once the flag is True
    
  2. If the robot has almost reached a goal but you are not exactly aligned with it and you are still moving forward at a constant speed, you might miss the goal (eventhough I don't know which tolerance you have) but to avoid that it is possible to also set the linear speed as the angular speed, but instead of the angular difference it would be the distance. Like that your robot will slow down when getting closer to the goal, letting you the time to adjust your orientation before missing the goal.


EDIT :

So if I understand the values right, if the coordinates are on the robots right side, I get a value between 0.0 and -pi returned and if its on the left its between 0.0 and pi? How do I effectively get which angle is smaller?

If you get the correct angle then yes but you won't get it simply with angle_to_goal - theta because angle_to_goal and theta will return a value between [-pi; pi] : If you have x > 0 a little angle, if you get angle_to_goal = pi - x and theta = -pi + x then angle_to_goal - theta = 2pi + 2x instead of 2x. So you have to modify the result of this angular difference, here's an exemple of a function (in C++) doing that :

float shortestAngleDifference(float th1, float th2)
{
    float anglediff = fmod( (th1 - th2) , 2*M_PI);

    if( anglediff < 0.0 )
    {
        if( fabs(anglediff) > (2*M_PI + anglediff) )
        {
            anglediff = 2*M_PI + anglediff;
        }
    }
    else
    {
        if( anglediff > fabs(anglediff - 2*M_PI) )
        {
            anglediff = anglediff - 2*M_PI;
        }
    }
    return anglediff;
}

Where angle_to_goal = th1 and theta = th2 to direclty have anglediff > 0 if the robot has to turn to the left and anglediff < 0 to turn to the right.

Note : In your solution you have multiple conditions to keep your speed between [0.3;0.7] but you could simply do :

if move_forward == True:
    #keep speed between 0.3 and 0.7
    if dist > 0.7:
        dist = 0.7
    elif dist < 0.3:
        dist = 0.3
    
    speed.linear.x = dist

Originally posted by Delb with karma: 3907 on 2019-01-22

This answer was ACCEPTED on the original site

Post score: 3

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.