Python Basics for Robotics Laser Identification issue

Hello, I was trying to figure out which laser index corresponds to the front, back, left and right and I was doing it by orienting the robot so that the front of the robot was close to the wall and reading the minimum distance measured and which laser index it corresponded to. I repeated this for left, right and back by orienting the robot such that the left, right and back are closest to the wall and seeing the laser index with the minimum distance. This worked in simulation, however, in trying this with the FastBot, I got the same laser index with minimum distance regardless of the orientation of t he robot which I believe should not be possible if different directions of the robot (and therefore different laser indices) are closest to a wall. I have attached my code below if it helps. I would appreciate any help regarding this.

#!/usr/bin/python3

# python imports
import time
import threading
import traceback
from math import inf
import statistics as stat
# ros2 imports
import rclpy
from rclpy.executors import MultiThreadedExecutor
# module imports
from robot_interface import RobotInterface


#~#~#~#~#~# start your class after this line #~#~#~#~#~#

class RobotControl:

    def __init__(self, robot_interface):
        # class constructor
        # write your initialization codes here if any
        self.robot_interface = robot_interface
        self.initial_position = dict()
        self.initial_orientation = dict()
        self.current_position = dict()
        self.current_orientation = dict()
        self.total_distance = 0.0
        self.start_time = time.time()
        return None
    
    def __del__(self):
        # class destructor
        # write your termination codes here if any
        return None
    
    #~#~#~#~#~# add your functions after this line #~#~#~#~#~#
    def get_linear_and_angular_velocities(self):
        return self.robot_interface.linear_velocity, self.robot_interface.angular_velocity

    def stop_robot(self):
        self.robot_interface.linear_velocity = 0.0
        self.robot_interface.angular_velocity = 0.0

    def move_robot_front(self, linear_speed):
        self.robot_interface.angular_velocity = 0.0
        self.robot_interface.linear_velocity = linear_speed

    def move_robot_back(self, linear_speed):
        self.robot_interface.angular_velocity = 0.0
        self.robot_interface.linear_velocity = -1.0 * linear_speed

    def turn_robot_left(self, angular_speed):
        self.robot_interface.linear_velocity = 0.0
        self.robot_interface.angular_velocity = angular_speed

    def turn_robot_right(self, angular_speed):
        self.robot_interface.linear_velocity = 0.0
        self.robot_interface.angular_velocity = -1.0 * angular_speed

    def timed_move_front(self, linear_speed, time_to_move):
        self.move_robot_front(linear_speed)
        time.sleep(0.3)
        print("Moving forward at ", linear_speed, " m/s for ", time_to_move, " s")
        time.sleep(time_to_move)
        self.stop_robot()

    def timed_move_back(self, linear_speed, time_to_move):
        self.move_robot_back(linear_speed)
        time.sleep(0.3)
        print("Moving backward at ", linear_speed, " m/s for ", time_to_move, " s")
        time.sleep(time_to_move)
        self.stop_robot()

    def timed_turn_left(self, angular_speed, time_to_move):
        self.turn_robot_left(angular_speed)
        time.sleep(0.3)
        print("Turning left at ", angular_speed, " rad/s for ", time_to_move, " s")
        time.sleep(time_to_move)
        self.stop_robot()

    def timed_turn_right(self, angular_speed, time_to_move):
        self.turn_robot_right(angular_speed)
        time.sleep(0.3)
        print("Turning right at ", angular_speed, " rad/s for ", time_to_move, " s")
        time.sleep(time_to_move)
        self.stop_robot()

    def move_distance_front(self, linear_speed, distance_to_move_cm):
        distance_to_move_m = distance_to_move_cm / 100.0
        time_to_move = distance_to_move_m / linear_speed
        print("Moving forward by ", distance_to_move_cm, " cm at ", linear_speed, " m/s")
        self.timed_move_front(linear_speed, time_to_move)

    def move_distance_back(self, linear_speed, distance_to_move_cm):
        distance_to_move_m = distance_to_move_cm / 100.0
        time_to_move = distance_to_move_m / linear_speed
        print("Moving backward by ", distance_to_move_cm, " cm at ", linear_speed, " m/s")
        self.timed_move_back(linear_speed, time_to_move)

    def turn_angle_left(self, angular_speed, angle_to_move):
        time_to_move = angle_to_move / angular_speed
        print("Turning left by ", angle_to_move, " rads at ", angular_speed, " rad/s")
        self.timed_turn_left(angular_speed, time_to_move)

    def turn_angle_right(self, angular_speed, angle_to_move):
        time_to_move = angle_to_move / angular_speed
        print("Turning left by ", angle_to_move, " rads at ", angular_speed, " rad/s")
        self.timed_turn_right(angular_speed, time_to_move)

    def get_minimum_scan_angle(self):
        return self.robot_interface.scan_angle_min

    def get_maximum_scan_angle(self):
        return self.robot_interface.scan_angle_max

    def get_angle_increment(self):
        return self.robot_interface.scan_angle_increment

    def get_minimum_scan_range(self):
        return self.robot_interface.scan_range_min

    def get_maximum_scan_range(self):
        return self.robot_interface.scan_range_max

    def get_all_scan_ranges(self):
        return self.robot_interface.scan_ranges

    def get_scan_range_by_index(self, index):
        scan_ranges = self.get_all_scan_ranges()
        return scan_ranges[index]

    # TODO
    def get_front_scan_range(self):
        scan_ranges = self.get_all_scan_ranges()
        front_scan_ranges = scan_ranges[248:413]
        minimum_front_scan_range = inf
        for front_scan_range in front_scan_ranges:
            if front_scan_range == inf:
                continue
            if front_scan_range < minimum_front_scan_range:
                minimum_front_scan_range = front_scan_range
        return minimum_front_scan_range

    # TODO
    def get_back_scan_range(self):
        scan_ranges = self.get_all_scan_ranges()
        back_scan_ranges = scan_ranges[578:] + scan_ranges[0:83]
        minimum_back_scan_range = inf
        for back_scan_range in back_scan_ranges:
            if back_scan_range == inf:
                continue
            if back_scan_range < minimum_back_scan_range:
                minimum_back_scan_range = back_scan_range
        return minimum_back_scan_range

    # TODO
    def get_left_scan_range(self):
        scan_ranges = self.get_all_scan_ranges()
        left_scan_ranges = scan_ranges[413:578]
        minimum_left_scan_range = inf
        for left_scan_range in left_scan_ranges:
            if left_scan_range == inf:
                continue
            if left_scan_range < minimum_left_scan_range:
                minimum_left_scan_range = left_scan_range
        return minimum_left_scan_range
        

    # TODO
    def get_right_scan_range(self):
        scan_ranges = self.get_all_scan_ranges()
        right_scan_ranges = scan_ranges[83:248]
        minimum_right_scan_range = inf
        for right_scan_range in right_scan_ranges:
            if right_scan_range == inf:
                continue
            if right_scan_range < minimum_right_scan_range:
                minimum_right_scan_range = right_scan_range
        return minimum_right_scan_range

    def get_minimum_range_ne_infinity_with_index(self):
        scan_ranges = self.get_all_scan_ranges()
        index_of_minimum_range = -1
        minimum_range = inf
        for index, scan_range in enumerate(scan_ranges):
            if scan_range == inf:
                continue

            if scan_range < minimum_range:
                minimum_range = scan_range
                index_of_minimum_range = index

        return index_of_minimum_range, minimum_range

    def get_maximum_range_ne_infinity_with_index(self):
        scan_ranges = self.get_all_scan_ranges()
        index_of_maximum_range = -1
        maximum_range = -inf
        for index, scan_range in enumerate(scan_ranges):
            if scan_range == inf:
                continue
                
            if scan_range > maximum_range:
                maximum_range = scan_range
                index_of_maximum_range = index

        return index_of_maximum_range, maximum_range

    def get_current_position_x_y_z(self):
        return {"x": self.robot_interface.odom_position_x, "y": self.robot_interface.odom_position_y, "z": self.robot_interface.odom_position_z}

    def get_current_orientation_r_p_y(self):
        return {"r": self.robot_interface.odom_orientation_r, "p": self.robot_interface.odom_orientation_p, "y": self.robot_interface.odom_orientation_y}

    def get_distance_using_euclidean_formula(self, x1, y1, x2, y2):
        distance_squared = (x2 - x1)**2 + (y2 - y1)**2
        return distance_squared**0.5

    def display_statistics(self):
        lin_vel, ang_vel = self.get_linear_and_angular_velocities()
        print("Linear Velocity: ", lin_vel, " m/s  Angular Velocity: ", ang_vel, " rad/s")
        print("Number of scan samples: ", len(self.get_all_scan_ranges()))
        print("Minimum scan angle: ", self.get_minimum_scan_angle(), " rads")
        print("Maximum scan angle: ", self.get_maximum_scan_angle(), " rads")
        print("Scan angle increment: ", self.get_angle_increment(), " rads")
        print("Minumum scan range: ", self.get_minimum_scan_range(), " m")
        print("Maxumum scan range: ", self.get_maximum_scan_range(), " m")
        print("Front scan range: ", self.get_front_scan_range(), " m")
        print("Back scan range: ", self.get_back_scan_range(), " m")
        print("Left scan range: ", self.get_left_scan_range(), " m")
        print("Right scan range: ", self.get_right_scan_range(), " m")
        min_range_index, min_range = self.get_minimum_range_ne_infinity_with_index()
        print("Minimum range: ", min_range, " m  Index: ", min_range_index)
        max_range_index, max_range = self.get_maximum_range_ne_infinity_with_index()
        print("Maximum range: ", max_range, " m  Index: ", max_range_index)
        print("Position (m): ", self.get_current_position_x_y_z())
        print("Orientation (rads): ", self.get_current_orientation_r_p_y())

    def obstacle_prediction(self, scan_ranges):
        front_scan_ranges = scan_ranges[248:413]

        front_left_scan_ray_index = 412
        front_right_scan_ray_index = 248
        front_left_scan_ranges = scan_ranges[248:330]
        front_right_scan_ranges = scan_ranges[330:413]

        filtered_front_scan_ranges = []
        for front_scan_range in front_scan_ranges:
            if front_scan_range == inf:
                continue
            filtered_front_scan_ranges.append(front_scan_range)
        
        threshold = 0.300
        if min(filtered_front_scan_ranges) > threshold:
            print("None")
        else:
            if len(stat.multimode(filtered_front_scan_ranges)) > 1:
                print("Obstacle")
            else:
                print("Wall")

    def direction_tracking(self, current_orientation):
        yaw = current_orientation["y"]
        PI = 3.14159

        tolerance_rads = 0.1
        north_rads = 0.0
        north_west_rads = PI / 4
        north_east_rads = -1.0 * PI / 4
        west_rads = PI / 2
        south_west_rads = (PI / 2) + (PI / 4)
        south_plus_rads = PI
        south_minus_rads = -1.0 * PI
        east_rads = -1.0 * PI / 2
        south_east_rads = -1.0 * ((PI / 2) + (PI / 4))

        direction_string = ""
        if yaw <= (north_rads + tolerance_rads) and yaw >= (north_rads - tolerance_rads):
            direction_string = "-N-"
        elif yaw > (north_rads + tolerance_rads) and yaw < (north_west_rads - tolerance_rads):
            direction_string = "NNW"
        elif yaw <= (north_west_rads + tolerance_rads) and yaw >= (north_west_rads - tolerance_rads):
            direction_string = "N-W"
        elif yaw > (north_west_rads + tolerance_rads) and yaw < (west_rads - tolerance_rads):
            direction_string = "WNW"
        elif yaw <= (west_rads + tolerance_rads) and yaw >= (west_rads - tolerance_rads):
            direction_string = "-W-"
        elif yaw > (west_rads + tolerance_rads) and yaw < (south_west_rads - tolerance_rads):
            direction_string = "WSW"
        elif yaw <= (south_west_rads + tolerance_rads) and yaw >= (south_west_rads - tolerance_rads):
            direction_string = "S-W"
        elif yaw > (south_west_rads + tolerance_rads) and yaw < (south_plus_rads - tolerance_rads):
            direction_string = "SSW"
        elif yaw >= (south_plus_rads - tolerance_rads) or yaw <= (south_minus_rads + tolerance_rads):
            direction_string = "-S-"
        elif yaw <= (north_east_rads + tolerance_rads) and yaw >= (north_east_rads - tolerance_rads):
            direction_string = "N-E"
        elif yaw > (north_east_rads + tolerance_rads) and yaw < (north_rads - tolerance_rads):
            direction_string = "NNE"
        elif yaw > (east_rads + tolerance_rads) and yaw < (north_east_rads - tolerance_rads):
            direction_string = "ENE"
        elif yaw <= (east_rads + tolerance_rads) and yaw >= (east_rads - tolerance_rads):
            direction_string = "-E-"
        elif yaw > (south_east_rads + tolerance_rads) and yaw < (east_rads - tolerance_rads):
            direction_string = "ESE"
        elif yaw <= (south_east_rads + tolerance_rads) and yaw >= (south_east_rads - tolerance_rads):
            direction_string = "S-E"
        elif yaw > (south_minus_rads + tolerance_rads) and yaw < (south_east_rads - tolerance_rads):
            direction_string = "SSE"
        else:
            direction_string = "N/A"
        print("Current direction: ", direction_string)

    def initialise_position_and_orientation(self):
        self.initial_orientation = self.get_current_orientation_r_p_y()
        self.initial_position = self.get_current_position_x_y_z()

    def remove_inf_from_list(self, input_list):
        list_to_return = list()
        for var in input_list:
            if var == inf:
                continue    
            list_to_return.append(var)
        return list_to_return

    def execute(self):
        if len(self.initial_orientation) == 3 and len(self.initial_position) == 3:
            # TODO
            scan_ranges = self.get_all_scan_ranges()
            number_of_lasers = len(scan_ranges)
            front_centre = scan_ranges[289:371]
            back_centre = scan_ranges[:41] + scan_ranges[618:]
            right_centre = scan_ranges[124:206]
            left_centre = scan_ranges[454:536]
            front_right = scan_ranges[206:289]
            front_left = scan_ranges[371:454]
            back_left = scan_ranges[536:618]
            back_right = scan_ranges[41:124]
            
            threshold = 0.300
            front_centre_blocked = min(self.remove_inf_from_list(front_centre)) < threshold
            front_left_blocked = min(self.remove_inf_from_list(front_left)) < threshold
            front_right_blocked = min(self.remove_inf_from_list(front_right)) < threshold

            current_time = time.time()
            if current_time - self.start_time > 1:
                print("There are ", number_of_lasers, " lasers")
                self.current_position = self.get_current_position_x_y_z()
                print("Current Position: ", self.current_position)
                self.direction_tracking(self.get_current_orientation_r_p_y())
                self.obstacle_prediction(scan_ranges)
                self.start_time = current_time

            if not front_centre_blocked:
                if front_left_blocked:
                    print("Front left blocked. Turning right")
                    self.stop_robot()
                    self.turn_angle_right(0.1, 0.5)
                    print("Moving forward now")
                    self.move_robot_front(0.1)
                elif front_right_blocked:
                    print("Front right blocked. Turning left")
                    self.stop_robot()
                    self.turn_angle_left(0.1, 0.5)
                    print("Moving forward now")
                    self.move_robot_front(0.1)
                else:
                    self.move_robot_front(0.1)
            else:
                if min(self.remove_inf_from_list(left_centre)) > min(self.remove_inf_from_list(right_centre)):
                    print("Forward blocked. More space on the left. Turning left")
                    self.stop_robot()
                    self.turn_angle_left(0.1, 0.5)
                    print("Moving forward now")
                    self.move_robot_front(0.1)
                else:
                    print("Forward blocked. More space on the right. Turning right")
                    self.stop_robot()
                    self.turn_angle_right(0.1, 0.5)
                    print("Moving forward now")
                    self.move_robot_front(0.1)





#~#~#~#~#~# finish your class before this line #~#~#~#~#~#

def spin_node():
    """
    make the robot interface program to run in a separate thread
    NOTE: THE ROBOT WILL NOT WORK IF THIS FUNCTION IS REMOVED !!!
    """
    global executor
    executor.spin()
    return None


if __name__ == "__main__":

    # initialize ros2 with python
    rclpy.init(args=None)
    # instantiate robot interface program module
    robot_interface = RobotInterface()
    # start robot interface program execution
    executor = MultiThreadedExecutor(num_threads=6)
    executor.add_node(robot_interface)
    # run robot interface program in a separate thread
    threading.Thread(target=spin_node).start()
    # wait for a few seconds for program to initialize
    print("Getting Ready in 5 Seconds...")
    time.sleep(5.0)
    print("READY !!!")

    try:
        #~#~#~#~#~# start your program after this line #~#~#~#~#~#

        ########## |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ##########
        ########## | ! YOU ARE ON YOUR OWN FROM HERE ! | ##########
        ########## |~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~| ##########

        #~#~#~#~#~# write code here to run only once #~#~#~#~#~#
        rc = RobotControl(robot_interface)
        rc.initialise_position_and_orientation()

        #~#~#~#~#~# write code here to run continuously #~#~#~#~#~#
        while True:
            # threshold = 0.5
            # front_range = rc.get_front_scan_range()
            # right_range = rc.get_right_scan_range()
            # left_range = rc.get_left_scan_range()
            # back_range = rc.get_back_scan_range()
            
            # if front_range > threshold:
            #     rc.move_robot_front(0.1)
            # else:
            #     rc.stop_robot()
            #     if right_range > left_range:
            #         rc.turn_angle_right(0.5, 0.4)
            #     else:
            #         rc.turn_angle_left(0.5, 0.4)

            # rc.display_statistics()
            # time.sleep(0.5)
            # rc.turn_angle_left(0.1, 0.1)
            # rc.direction_tracking()
            # rc.execute()
            # minimum_range_index, minimum_range = rc.get_minimum_range_ne_infinity_with_index()
            # print("Minimum range index: ", minimum_range_index, "   Minimum range: ", minimum_range, " m")
            # time.sleep(1)
            # rc.move_distance_front(0.1, 100)
            # rc.move_robot_front(0.1)
            print(rc.get_minimum_range_ne_infinity_with_index())
            # print(rc.get_all_scan_ranges()[10])
            time.sleep(1)
        
        #~#~#~#~#~# finish your program before this line #~#~#~#~#~#

    except Exception as error:
        # report exception
        print("~~~~~~~~~~~ ERROR: ~~~~~~~~~~~")
        print(traceback.print_exception(error))
        print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
        # clean up before shutdown
        executor.shutdown()
        robot_interface.destroy_node()

    finally:
        # shutdown ros2
        rclpy.shutdown()

# End of Code

Hi @janimahmood22 ,

Thanks for reporting this issue. I personally connected to the robot and tested the laser scan values myself.
It seems to be a problem with the robot’s laser scan. It reports a faulty value. I think the laser scanner needs to be replaced or fixed by hardware replacement.

Here is a screenshot that I took, showing the error: there is no object about 10 cm around the robot and it reports that one index above 200 is having a value about 10 cm.

Now that this issue is detected, it still does not stop you from proceeding with your program.
The fastbot dimensions are 15.5 x 15 cm, that is, left to right is 15 cm (wheel to wheel), and front to back is 15.5 cm.
The laser scanner is situated in the geometric center of the robot, so you have front and back distance minimums as 15.5/2 = 7.75 cm, left and right minimums as 15/2 = 7.5 cm.

So when you are doing this experiment again, make the robot go close enough to the wall, but not touching the wall, essentially having more than 15 cm gap between the laser and the wall.
this way, you can also exclude any values under 15 cm, and check for the range closest to 15 cm, just above 15 cm on all 4 directions. Then you can get the indexes that correspond to the directions of the robot.

But yes, the laser scanner is faulty on the robot for sure. But for this small anomaly, the laser scanner cannot be changed. I hope you understand this fact. Please proceed with your project with the suggestion I have provided you above (exclude values under 15 or 12 cm).

I hope this solves your issue.

Regards,
Girish

Hi @janimahmood22 ,

I just realized something - the one index that always shows close to 10 cm is not the fault of the laser scanner.
It is the result of the robot construction - the antenna pole on the back of the robot is the culprit here.

So you will always have 1 or two readings that give a fixed constant low value.
Yet again, the workaround is to ignore the values less than the distance to the antenna pole, which is oscillating around 10 cm.
Your solution: just consider all values above 12 cm and under infinity.

This should fix your problem.

Also, considering the fact that there will be loose wires hanging about on the back of the robot, my recommendation here is that, it is best not to use any laser readings from the back of the robot for navigation purposes.

Regards,
Girish

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