Ejemplo n.º 1
0
    def __init__(self, input_server, init_input=None):
        self.run_mock = init_input is not None

        self.master: Tk = Tk()
        self.canvas: Canvas = None
        self.tile_dict: Dict[Tile, int] = None
        self.grid = grid.Grid(tile_num_height, tile_num_width, tile_size)
        self.last_iter_seen = set()
        self.heading: int = 0
        self.curr_tile = self.grid.grid[int(self.grid.num_rows / 2)][int(
            self.grid.num_cols / 2)]

        # planned path of tiles
        self.prev_draw_c1c0_ids = [None, None]
        self.create_widgets()
        self.server = input_server
        self.processEndPoint(self.server.recieve_data_init()['end_point'])
        #print('got the end point to be, ', self.endPoint)
        self.path = search.a_star_search(self.grid,
                                         (self.curr_tile.x, self.curr_tile.y),
                                         self.endPoint, search.euclidean)
        self.path = search.segment_path(self.grid, self.path)
        self.path_set = set()
        self.generatePathSet()
        self.pathIndex = 0
        self.prev_tile = None
        self.prev_vector = None
        self.way_point = None
        self.loop_it = 0

        self.prev_line_id = []
        self.set_of_prev_path = []
        self.color_list = [
            '#2e5200', '#347800', '#48a600', '#54c200', '#60de00', 'None'
        ]
        self.index_fst_4 = 0
        self.drawPath()
        self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                       self.curr_tile.y)
        self.drawWayPoint(self.path[self.pathIndex])
        self.updateDesiredHeading(self.path[self.pathIndex])
        self.gps = GPS(self.grid, self.pid)
        self.prev_tile, self.curr_tile = self.gps.update_loc(self.curr_tile)
        self.main_loop()
        if self.curr_tile == self.path[-1]:
            print("Reached endpoint")
            self.hedge.stop()
            return
        self.master.mainloop()
Ejemplo n.º 2
0
    def main_loop(self):
        """
        updates the grid in a smoothed fashion
        """
        if self.curr_tile == self.gridEmpty.get_tile(self.endPoint):
            print('C1C0 has ARRIVED AT THE DESTINATION, destination tile')
            return
        lidar_data = self.generate_sensor.generateLidar(
            degree_freq, self.curr_tile.row, self.curr_tile.col)
        self.recalc = self.gridEmpty.update_grid_tup_data(
            self.curr_x, self.curr_y, lidar_data, Tile.lidar, robot_radius,
            bloat_factor, self.pathSet)
        self.turn()
        self.recalc_cond = self.recalc_cond or self.recalc

        # if (self.curr_tile.is_obstacle and self.curr_tile.bloat_score > 6) or \
        #         (self.curr_tile.is_obstacle and not self.curr_tile.is_bloated):
        #     self.emergency_step(lidar_data)
        #     self.recalc_cond = True
        # else:
        # if we need to recalculate then recurse
        if self.recalc_cond and self.recalc_count >= recalc_wait:
            try:
                self.recalculate_path(lidar_data)
            except Exception as e:
                print('error occured, ', e)
                self.emergency_step(lidar_data)
                self.recalc_cond = True
        elif self.nextLoc():
            self.mean = random.randint(-1, 1) / 12.0
            self.standard_deviation = random.randint(0, 1) / 10.0
            self.pathIndex += 1
            self.next_tile = self.path[self.pathIndex]
            self.drawWayPoint(self.next_tile)
            self.updateDesiredHeading()
            self.pid = PID(self.path, self.pathIndex + 1, self.curr_x,
                           self.curr_y)
        else:
            #print(self.path[self.pathIndex].row, self.path[self.pathIndex].col)
            #print(self.path[self.pathIndex+1].row, self.path[self.pathIndex+1].col)
            #print(self.curr_tile.is_bloated)
            self.step(lidar_data)
        self.drawC1C0()
        self.master.after(fast_speed_dynamic, self.main_loop)
Ejemplo n.º 3
0
    def init_phase(self):
        curr_tile = self.path[0]
        self.prev_tile = self.curr_tile
        self.curr_tile = curr_tile
        self.curr_x = self.curr_tile.x
        self.curr_y = self.curr_tile.y
        self.prev_x = self.curr_tile.x
        self.prev_y = self.curr_tile.y

        self.visitedSet.add(curr_tile)
        self.generatePathSet()
        lidar_data = self.generate_sensor.generateLidar(
            degree_freq, curr_tile.row, curr_tile.col)
        self.generatePathSet()
        self.recalc = self.gridEmpty.update_grid_tup_data(
            curr_tile.x, curr_tile.y, lidar_data, Tile.lidar, robot_radius,
            bloat_factor, self.pathSet)
        self.next_tile = self.path[1]
        self.visibilityDraw(lidar_data)
        self.updateDesiredHeading()
        self.pid = PID(self.path, self.pathIndex + 1, self.curr_x, self.curr_y)

        self.master.after(fast_speed_dynamic, self.main_loop)
Ejemplo n.º 4
0
 def recalculate_path(self, lidar_data):
     self.path = search.a_star_search(self.gridEmpty,
                                      (self.curr_x, self.curr_y),
                                      self.endPoint, search.euclidean)
     self.path = search.segment_path(self.gridEmpty, self.path)
     self.pathIndex = 0
     self.smoothed_window.path = self.path
     self.smoothed_window.drawPath()
     self.draw_line(self.curr_x, self.curr_y, self.path[0].x,
                    self.path[0].y)
     self.prev_tile = self.curr_tile
     self.curr_tile = self.path[0]
     self.curr_x = self.curr_tile.x
     self.curr_y = self.curr_tile.y
     self.next_tile = self.path[1]
     self.updateDesiredHeading()
     self.generatePathSet()
     self.visibilityDraw(lidar_data)
     self.pathIndex = 0
     self.recalc = False
     self.recalc_count = 0
     self.recalc_cond = False
     self.drawWayPoint(self.next_tile)
     self.pid = PID(self.path, self.pathIndex + 1, self.curr_x, self.curr_y)
Ejemplo n.º 5
0
    def main_loop(self):
        """
        """
        self.loop_it += 1
        if self.loop_it % 6 == 0:
            self.refresh_bloating()
        # update location based on indoor GPS
        self.prev_tile, self.curr_tile = self.gps.update_loc(self.curr_tile)
        self.drawC1C0()
        if self.run_mock:
            self.server.send_update((self.curr_tile.row, self.curr_tile.col))
        else:
            motor_speed = self.computeMotorSpeed()
            self.server.send_update(motor_speed)
        #  TODO 2: Update environment based on sensor data
        self.sensor_state = self.server.receive_data()
        self.update_grid_wrapper()
        self.visibilityDraw(self.filter_lidar(self.sensor_state.lidar))

        if self.grid.update_grid_tup_data(
                self.curr_tile.x, self.curr_tile.y,
                self.filter_lidar(self.sensor_state.lidar), Tile.lidar,
                robot_radius, bloat_factor, self.path_set):
            self.generatePathSet()
            #print('current location x', self.curr_tile.x)
            #print('current location y', self.curr_tile.y)
            try:
                self.path = search.a_star_search(
                    self.grid, (self.curr_tile.x, self.curr_tile.y),
                    self.endPoint, search.euclidean)
                self.path = search.segment_path(self.grid, self.path)
                self.pathIndex = 0
                self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                               self.curr_tile.y)
                self.drawWayPoint(self.path[self.pathIndex])
                self.updateDesiredHeading(self.path[self.pathIndex])
                self.generatePathSet()
            except Exception as e:
                print(e, 'in an obstacle right now... oof ')

        # recalculate path if C1C0 is totally off course (meaning that PA + PB > 2*AB)
        if self.pathIndex != 0:
            # distance to previous waypoint
            dist1 = (self.curr_tile.x - self.path[self.pathIndex - 1].x)**2 + (
                self.curr_tile.y - self.path[self.pathIndex - 1].y)**2
            # distance to next waypoint
            dist2 = (self.curr_tile.x - self.path[self.pathIndex].x)**2 + (
                self.curr_tile.y - self.path[self.pathIndex].y)**2
            # distance between waypoints
            dist = (self.path[self.pathIndex-1].x - self.path[self.pathIndex].x) ** 2\
                + (self.path[self.pathIndex-1].y -
                   self.path[self.pathIndex].y) ** 2
            if 4 * dist < dist1 + dist2:
                try:
                    self.path = search.a_star_search(
                        self.grid, (self.curr_tile.x, self.curr_tile.y),
                        self.endPoint, search.euclidean)
                    self.path = search.segment_path(self.grid, self.path)
                    self.pathIndex = 0
                    self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                                   self.curr_tile.y)
                    self.generatePathSet()
                except Exception as e:
                    print(e, 'in an obstacle right now... oof ')

        self.drawPath()

        self.calcVector()
        if self.nextLoc():
            self.pathIndex += 1
            if self.pathIndex >= len(self.path):
                return
            self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                           self.curr_tile.y)
            self.drawWayPoint(self.path[self.pathIndex])
            self.updateDesiredHeading(self.path[self.pathIndex])
        # return if we are at the end destination
        if self.curr_tile == self.path[-1] and abs(self.heading -
                                                   self.desired_heading) <= 2:
            return
        # recursively loop
        self.master.after(1, self.main_loop)
Ejemplo n.º 6
0
class ServerGUI:
    """
    Master file to run autonomous path planning and display visualization real-time
    Instance Attributes:
        master (Tk): Tkinter GUI object
        canvas (Canvas): Tkinter Canvas that represents the GUI
        tile_dict (Dict[Tile, Rectangle]): Mapping from tiles to rectangles on GUI
        grid (Grid): grid that represents the environment
        heading (int): integer to represent the angle that the robot is facing
    """
    def __init__(self, input_server, init_input=None):
        self.run_mock = init_input is not None

        self.master: Tk = Tk()
        self.canvas: Canvas = None
        self.tile_dict: Dict[Tile, int] = None
        self.grid = grid.Grid(tile_num_height, tile_num_width, tile_size)
        self.last_iter_seen = set()
        self.heading: int = 0
        self.curr_tile = self.grid.grid[int(self.grid.num_rows / 2)][int(
            self.grid.num_cols / 2)]

        # planned path of tiles
        self.prev_draw_c1c0_ids = [None, None]
        self.create_widgets()
        self.server = input_server
        self.processEndPoint(self.server.recieve_data_init()['end_point'])
        #print('got the end point to be, ', self.endPoint)
        self.path = search.a_star_search(self.grid,
                                         (self.curr_tile.x, self.curr_tile.y),
                                         self.endPoint, search.euclidean)
        self.path = search.segment_path(self.grid, self.path)
        self.path_set = set()
        self.generatePathSet()
        self.pathIndex = 0
        self.prev_tile = None
        self.prev_vector = None
        self.way_point = None
        self.loop_it = 0

        self.prev_line_id = []
        self.set_of_prev_path = []
        self.color_list = [
            '#2e5200', '#347800', '#48a600', '#54c200', '#60de00', 'None'
        ]
        self.index_fst_4 = 0
        self.drawPath()
        self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                       self.curr_tile.y)
        self.drawWayPoint(self.path[self.pathIndex])
        self.updateDesiredHeading(self.path[self.pathIndex])
        self.gps = GPS(self.grid, self.pid)
        self.prev_tile, self.curr_tile = self.gps.update_loc(self.curr_tile)
        self.main_loop()
        if self.curr_tile == self.path[-1]:
            print("Reached endpoint")
            self.hedge.stop()
            return
        self.master.mainloop()

    def processEndPoint(self, endPoint):
        """
        Processes the endpoint and returns the corresponding tuple.
        Input examples:
            ("move forward", m) where m is in meters
            ("turn", deg) where deg is positive for clockwise turns, negative for counterclockwise
            (x, y) where x and y are coordinates to move to 
        Updates self.endPoint to be the final coordinates to move to,
        and updates self.desired_heading
        """
        #    firstNum = firstNum + tile_num_width * tile_size / 2
        #    secondNum = -secondNum + tile_num_height * tile_size / 2
        start = endPoint.find("(")
        comma = endPoint.find(",")
        end = endPoint.find(")")
        processedEndPoint = (endPoint[start + 1:comma],
                             float(endPoint[comma + 2:end]))
        """
        print(
            f"EndPoint[0]: {processedEndPoint[0]}    EndPoint[1]: {processedEndPoint[1]}")
        """
        if processedEndPoint[0] == "'move forward'":
            self.endPoint = (self.curr_tile.x,
                             self.curr_tile.y + processedEndPoint[1] * 100)
            self.desired_heading = self.heading
        elif processedEndPoint[0] == "'turn'":
            self.endPoint = (self.curr_tile.x, self.curr_tile.y)
            self.desired_heading = self.heading + processedEndPoint[1]
            #print(f"Ang[0]: {self.heading}    Ang[1]: {self.desired_heading}")
        else:
            self.endPoint = (self.curr_tile.x -
                             int(processedEndPoint[0]) * 100,
                             self.curr_tile.y + processedEndPoint[1] * 100)
            self.desired_heading = self.heading

    def create_widgets(self):
        """
        Creates the canvas of the size of the inputted grid
        """
        self.master.geometry("+900+100")
        canvas = Canvas(self.master,
                        width=len(self.grid.grid[0]) * GUI_tile_size,
                        height=len(self.grid.grid) * GUI_tile_size)
        offset = GUI_tile_size / 2
        tile_dict = {}
        for row in self.grid.grid:
            for tile in row:
                x = tile.x / tile_scale_fac
                y = tile.y / tile_scale_fac
                tile_dict[tile] = canvas.create_rectangle(
                    x - offset,
                    y - offset,
                    x + offset,
                    y + offset,
                    outline=tile.get_color(),
                    fill=tile.get_color())
        canvas.pack()
        self.canvas = canvas
        self.tile_dict = tile_dict

    def nextLoc(self):
        next_tile = self.path[self.pathIndex]
        d = math.sqrt((self.curr_tile.x - next_tile.x)**2 +
                      (self.curr_tile.y - next_tile.y)**2)
        return d <= reached_tile_bound and abs(self.heading -
                                               self.desired_heading) <= 2

    def updateDesiredHeading(self, next_tile):
        """
        Calculates the degrees between the current tile and the next tile and updates desired_heading. Estimates the
        degrees to the nearing int.
        """
        x_change = next_tile.x - self.curr_tile.x
        y_change = next_tile.y - self.curr_tile.y
        #print(f"x: {x_change}    y: {y_change}")
        if x_change == 0 and y_change == 0:
            # no movement, only turning command was given
            self.desired_heading = self.desired_heading
        else:
            # there's some movement necessary
            self.desired_heading = self.heading + \
                round(math.degrees(math.atan2(y_change, x_change))) - \
                90.0  # -90 fixes the transformed value
            if self.desired_heading < -180.0:
                self.desired_heading = self.desired_heading + 360
            elif self.desired_heading > 180.0:
                self.desired_heading = self.desired_heading - 360

    def computeMotorSpeed(self):
        """
        Currently assuming:
            if desired angle > current angle, turn right
            if desired angle < current angle, turn left
            Threshold of 2 degrees, will only try to rotate if the rotation
            is more than 2 degrees.
            Threshold of (5 centimeters, need to change after testing) for the x and y end points.
        """
        # TODO: Test angle and distance thresholds with C1C0
        """
        print(
            f"curr tile x: {self.curr_tile.x}    curr tile y {self.curr_tile.y}")
        print(
            f"end point x: {self.endPoint[0]}    end point y {self.endPoint[1]}")
        print(
            f"self.desired_heading: {self.desired_heading}    self.heading {self.heading}")
        """
        if abs(self.curr_tile.x - self.endPoint[0]) <= 30 and abs(
                self.curr_tile.y - self.endPoint[1]) <= 30 and (
                    abs(self.desired_heading - self.heading) <= 3):
            return ()
        elif self.desired_heading - self.heading > 3:
            return rotation_right
        elif self.desired_heading - self.heading < -3:
            return rotation_left
        else:
            return motor_speed

    def update_grid_wrapper(self):
        t_bot, t_mid, t_top = self.sensor_state.get_terabee()
        lidar_data = self.filter_lidar(self.sensor_state.lidar)

        lidar_ret = self.grid.update_grid_tup_data(self.curr_tile.x,
                                                   self.curr_tile.y,
                                                   lidar_data, Tile.lidar,
                                                   robot_radius, bloat_factor,
                                                   self.path_set)
        bot_ter_ret = self.grid.update_grid_tup_data(
            self.curr_tile.x, self.curr_tile.y, t_bot, Tile.bottom_terabee,
            robot_radius, bloat_factor, self.path_set)
        # mid_ter_ret = self.grid.update_grid_tup_data(self.curr_tile.x, self.curr_tile.y, t_mid, Tile.mid_terabee,
        #                                              robot_radius, bloat_factor, self.path_set)
        # top_ter_ret = self.grid.update_grid_tup_data(self.curr_tile.x, self.curr_tile.y, t_top, Tile.top_terabee,
        #                                              robot_radius, bloat_factor, self.path_set)
        self.heading = self.sensor_state.heading
        return lidar_ret and bot_ter_ret

    def filter_lidar(self, lidar):
        # print(lidar)
        lidar_ret = []
        for (ang, dist) in lidar:
            if dist > 400:
                lidar_ret.append(((ang + lidar_shift_ang) % 360, dist))
        return lidar_ret

    def filter_terabee(self, terabee):
        terabee_ret = []
        for (ang, dist) in terabee:
            if 100 < dist < 50000:
                terabee_ret.append((ang, dist))
        return terabee_ret

    def main_loop(self):
        """
        """
        self.loop_it += 1
        if self.loop_it % 6 == 0:
            self.refresh_bloating()
        # update location based on indoor GPS
        self.prev_tile, self.curr_tile = self.gps.update_loc(self.curr_tile)
        self.drawC1C0()
        if self.run_mock:
            self.server.send_update((self.curr_tile.row, self.curr_tile.col))
        else:
            motor_speed = self.computeMotorSpeed()
            self.server.send_update(motor_speed)
        #  TODO 2: Update environment based on sensor data
        self.sensor_state = self.server.receive_data()
        self.update_grid_wrapper()
        self.visibilityDraw(self.filter_lidar(self.sensor_state.lidar))

        if self.grid.update_grid_tup_data(
                self.curr_tile.x, self.curr_tile.y,
                self.filter_lidar(self.sensor_state.lidar), Tile.lidar,
                robot_radius, bloat_factor, self.path_set):
            self.generatePathSet()
            #print('current location x', self.curr_tile.x)
            #print('current location y', self.curr_tile.y)
            try:
                self.path = search.a_star_search(
                    self.grid, (self.curr_tile.x, self.curr_tile.y),
                    self.endPoint, search.euclidean)
                self.path = search.segment_path(self.grid, self.path)
                self.pathIndex = 0
                self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                               self.curr_tile.y)
                self.drawWayPoint(self.path[self.pathIndex])
                self.updateDesiredHeading(self.path[self.pathIndex])
                self.generatePathSet()
            except Exception as e:
                print(e, 'in an obstacle right now... oof ')

        # recalculate path if C1C0 is totally off course (meaning that PA + PB > 2*AB)
        if self.pathIndex != 0:
            # distance to previous waypoint
            dist1 = (self.curr_tile.x - self.path[self.pathIndex - 1].x)**2 + (
                self.curr_tile.y - self.path[self.pathIndex - 1].y)**2
            # distance to next waypoint
            dist2 = (self.curr_tile.x - self.path[self.pathIndex].x)**2 + (
                self.curr_tile.y - self.path[self.pathIndex].y)**2
            # distance between waypoints
            dist = (self.path[self.pathIndex-1].x - self.path[self.pathIndex].x) ** 2\
                + (self.path[self.pathIndex-1].y -
                   self.path[self.pathIndex].y) ** 2
            if 4 * dist < dist1 + dist2:
                try:
                    self.path = search.a_star_search(
                        self.grid, (self.curr_tile.x, self.curr_tile.y),
                        self.endPoint, search.euclidean)
                    self.path = search.segment_path(self.grid, self.path)
                    self.pathIndex = 0
                    self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                                   self.curr_tile.y)
                    self.generatePathSet()
                except Exception as e:
                    print(e, 'in an obstacle right now... oof ')

        self.drawPath()

        self.calcVector()
        if self.nextLoc():
            self.pathIndex += 1
            if self.pathIndex >= len(self.path):
                return
            self.pid = PID(self.path, self.pathIndex, self.curr_tile.x,
                           self.curr_tile.y)
            self.drawWayPoint(self.path[self.pathIndex])
            self.updateDesiredHeading(self.path[self.pathIndex])
        # return if we are at the end destination
        if self.curr_tile == self.path[-1] and abs(self.heading -
                                                   self.desired_heading) <= 2:
            return
        # recursively loop
        self.master.after(1, self.main_loop)

    def calcVector(self):
        """
        Returns the vector between the current location and the end point of the current line segment
        and draws this vector onto the canvas
        """
        #print('calc vector was called')
        vect = (0, 0)
        if self.pathIndex < len(self.path):
            vect = self.pid.newVec()
            if self.prev_vector is not None:
                # delete old drawings from previous iteration
                self.canvas.delete(self.prev_vector)
            start = self._scale_coords((self.curr_tile.x, self.curr_tile.y))
            end = self._scale_coords(
                (self.curr_tile.x + vector_draw_length * vect[0],
                 self.curr_tile.y + vector_draw_length * vect[1]))
            self.prev_vector = self.canvas.create_line(start[0],
                                                       start[1],
                                                       end[0],
                                                       end[1],
                                                       arrow='last',
                                                       fill='red')
        return vect

    def refresh_bloating(self):
        for row in self.grid.grid:
            for tile in row:
                if tile.is_bloated:
                    tile.is_bloated = False
                    tile.is_obstacle = False
        for row in self.grid.grid:
            for tile in row:
                if tile.is_obstacle and not tile.is_bloated:
                    self.grid.bloat_tile(tile, 80, 2, set())

    def visibilityDraw(self, lidar_data):
        """Draws a circle of visibility around the robot
       """
        # coloring all tiles that were seen in last iteration light gray
        # while self.last_iter_seen:
        #     curr_rec = self.last_iter_seen.pop()
        #     self.canvas.itemconfig(
        #         curr_rec, outline="#C7C7C7", fill="#C7C7C7")  # light gray
        row = self.curr_tile.row
        col = self.curr_tile.col
        index_radius_inner = int(vis_radius / tile_size)
        index_radius_outer = index_radius_inner + 2
        # the bounds for the visibility circle
        lower_row = max(0, row - index_radius_outer)
        lower_col = max(0, col - index_radius_outer)
        upper_row = min(row + index_radius_outer, self.grid.num_rows - 1)
        upper_col = min(col + index_radius_outer, self.grid.num_cols - 1)
        lidar_data_copy = copy.copy(lidar_data)
        rad_inc = int(GUI_tile_size / 3)  # radius increment to traverse tiles

        def _color_normally(r, angle_rad):
            """
           Colors the tile at the location that is at a distance r at heading angle_rad from robot's current location.
           Colors the tile based on its known attribute (obstacle, bloated, visited, or visible).
           """
            coords = (r * math.sin(angle_rad) + row,
                      r * math.cos(angle_rad) + col
                      )  # (row, col) of tile we want to color
            # make sure coords are in bounds of GUI window
            if (coords[0] >= lower_row) and (coords[0] <= upper_row) \
                    and (coords[1] >= lower_col) and (coords[1] <= upper_col):
                curr_tile = self.grid.grid[int(coords[0])][int(coords[1])]
                curr_rec = self.tile_dict[curr_tile]
                if curr_tile.is_bloated:
                    self.canvas.itemconfig(curr_rec,
                                           outline="#ffc0cb",
                                           fill="#ffc0cb")  # pink
                elif curr_tile.is_obstacle:
                    self.canvas.itemconfig(curr_rec,
                                           outline="#ff621f",
                                           fill="#ff621f")  # red
                else:
                    self.canvas.itemconfig(curr_rec,
                                           outline="#fff",
                                           fill="#fff")  # white
                    self.last_iter_seen.add(curr_rec)

        # iterating through 360 degrees surroundings of robot in increments of degree_freq
        for deg in range(0, 360, degree_freq):
            angle_rad = deg * math.pi / 180
            if len(lidar_data_copy) == 0 or lidar_data_copy[0][0] != deg:
                # no object in sight at deg; color everything normally up to visibility radius
                for r in range(0, index_radius_inner, rad_inc):
                    _color_normally(r, angle_rad)
            else:  # obstacle in sight
                # color everything normally UP TO obstacle, and color obstacle red
                for r in range(
                        0,
                        math.ceil(lidar_data_copy[0][1] / tile_size) + rad_inc,
                        rad_inc):
                    _color_normally(r, angle_rad)
                lidar_data_copy.pop(0)

    def drawC1C0(self):
        """Draws C1C0's current location on the simulation"""
        # coordinates of robot center right now (in cm)
        center_x = self.curr_tile.x
        center_y = self.curr_tile.y
        # converting heading to radians, and adjusting so that facing right = 0 deg
        heading_adj_rad = math.radians(self.heading + 90)
        if self.prev_draw_c1c0_ids is not None:
            # delete old drawings from previous iteration
            self.canvas.delete(self.prev_draw_c1c0_ids[0])
            self.canvas.delete(self.prev_draw_c1c0_ids[1])
        # coordinates of bounding square around blue circle
        top_left_coords = (center_x - robot_radius, center_y + robot_radius)
        bot_right_coords = (center_x + robot_radius, center_y - robot_radius)
        # convert coordinates from cm to pixels
        top_left_coords_scaled = self._scale_coords(top_left_coords)
        bot_right_coords_scaled = self._scale_coords(bot_right_coords)
        # draw blue circle
        self.prev_draw_c1c0_ids[0] = self.canvas.create_oval(
            top_left_coords_scaled[0],
            top_left_coords_scaled[1],
            bot_right_coords_scaled[0],
            bot_right_coords_scaled[1],
            outline='black',
            fill='blue')
        center_coords_scaled = self._scale_coords((center_x, center_y))
        # finding endpoint coords of arrow
        arrow_end_x = center_x + robot_radius * math.cos(heading_adj_rad)
        arrow_end_y = center_y + robot_radius * math.sin(heading_adj_rad)
        arrow_end_coords_scaled = self._scale_coords(
            (arrow_end_x, arrow_end_y))
        # draw white arrow
        self.prev_draw_c1c0_ids[1] = self.canvas.create_line(
            center_coords_scaled[0],
            center_coords_scaled[1],
            arrow_end_coords_scaled[0],
            arrow_end_coords_scaled[1],
            arrow='last',
            fill='white')

    def drawPath(self):
        # change previous 5 paths with green gradual gradient

        # set default/initial color
        color = self.color_list[4]

        # if there is any path that the bot walked through, it gets added to set_of_prev_path
        if self.prev_line_id:
            self.set_of_prev_path.append(self.prev_line_id)

        # if there is any previous path in the set_of_prev_path, then we check if there is less than 5 lines,
        # if so we change the color of the newest path to a color from the list. If there is more than 5 lines,
        # we delete the oldest line and change the colors of remaining previous colors to a lighter shade.
        if self.set_of_prev_path:
            if len(self.set_of_prev_path) > 4:
                for fst_id in self.set_of_prev_path[0]:
                    self.canvas.delete(fst_id)
                self.set_of_prev_path.pop(0)
            for x in range(len(self.set_of_prev_path)):
                for ids in self.set_of_prev_path[x]:
                    self.canvas.itemconfig(ids, fill=self.color_list[x])
        # clear current path
        self.prev_line_id = []

        # continuously draw segments of the path, and add it to the prev_line_id list
        idx = 1
        while idx < len(self.path):
            x1, y1 = self._scale_coords(
                (self.path[idx - 1].x, self.path[idx - 1].y))
            x2, y2 = self._scale_coords((self.path[idx].x, self.path[idx].y))
            canvas_id = self.canvas.create_line(x1,
                                                y1,
                                                x2,
                                                y2,
                                                fill=color,
                                                width=1.5)
            self.prev_line_id.append(canvas_id)
            idx += 1

    def _scale_coords(self, coords):
        """scales coords (a tuple (x, y)) from real life cm to pixels"""
        scaled_x = coords[0] / tile_scale_fac
        scaled_y = coords[1] / tile_scale_fac
        return scaled_x, scaled_y

    def generatePathSet(self):
        self.path_set = set()
        for i in range(len(self.path) - 1):
            self.breakUpLine(self.path[i], self.path[i + 1])

    def breakUpLine(self, curr_tile, next_tile):
        current_loc = (curr_tile.x, curr_tile.y)
        next_loc = (next_tile.x, next_tile.y)
        # calculate the slope, rise/run
        x_change = next_loc[0] - current_loc[0]
        y_change = next_loc[1] - current_loc[1]
        dist = math.sqrt(x_change**2 + y_change**2)

        # if (dist < tile_size):
        #     return [(current_loc[0] + x_change, current_loc[1] + y_change)]

        num_steps = int(dist / tile_size)
        returner = []

        if y_change == 0:
            x_step = tile_size
            y_step = 0
        elif x_change == 0:
            x_step = 0
            y_step = tile_size
        else:
            inv_slope = x_change / y_change
            # x^2+y^2 = tile_size^2    &&     x/y = x_change/y_change
            y_step = math.sqrt(tile_size**2 / (inv_slope**2 + 1))
            y_step = y_step
            x_step = math.sqrt(
                (tile_size**2 * inv_slope**2) / (inv_slope**2 + 1))
            x_step = x_step
        if x_change < 0 and x_step > 0:
            x_step = -x_step
        if y_change < 0 and y_step:
            y_step = -y_step

        for i in range(1, num_steps + 1):
            new_coor = (current_loc[0] + i * x_step,
                        current_loc[1] + i * y_step)
            returner.append(new_coor)
            new_tile = self.grid.get_tile(new_coor)
            if new_tile not in self.path_set:
                self.path_set.add(new_tile)

    def drawWayPoint(self, new_tile):
        if self.way_point is not None:
            self.canvas.delete(self.way_point)
        offset = GUI_tile_size
        x = new_tile.x / tile_scale_fac
        y = new_tile.y / tile_scale_fac
        self.way_point = self.canvas.create_oval(x - offset,
                                                 y - offset,
                                                 x + offset,
                                                 y + offset,
                                                 outline="#FF0000",
                                                 fill="#FF0000")
Ejemplo n.º 7
0
class DynamicGUI():
    def __init__(self, master, fullMap, emptyMap, path, endPoint):
        """A class to represent a GUI with a map

        Arguments:
            master {Tk} -- Tkinter GUI generator
            inputMap {grid} -- The grid to draw on
            path {tile list} -- the path of grid tiles visited

        FIELDS:
            master {Tk} -- Tkinter GUI generator
            tile_dict {dict} -- a dictionary that maps tiles to rectangles
            canvas {Canvas} -- the canvas the GUI is made on
            path {Tile list} -- the list of tiles visited by robot on path
            pathSet {Tile Set} -- set of tiles that have already been drawn (So GUI
                does not draw over the tiles)
            pathIndex {int} -- the index of the path the GUI is at in anumation
            curr_tile {Tile} -- the current tile the robot is at in animation
            grid {Grid} -- the Grid object that the simulation was run on
        """
        # Tinker master, used to create GUI
        self.master = master
        self.tile_dict = None
        self.canvas = None

        self.path = path

        self.brokenPath = None
        self.brokenPathIndex = 0

        self.visitedSet = set()
        self.pathSet = set()
        for i in self.path:
            self.pathSet.add(i)
        self.pathIndex = 0
        self.curr_tile = None
        self.prev_tile = None

        self.gridFull = fullMap
        self.gridEmpty = emptyMap

        self.recalc = False

        self.create_widgets()
        self.generate_sensor = GenerateSensorData(self.gridFull)

        self.endPoint = endPoint
        self.next_tile = None
        self.prev_vector = None

        self.recalc_count = recalc_wait
        self.recalc_cond = False
        self.way_point = None

        self.last_iter_seen = set(
        )  # set of tiles that were marked as available path in simulation's previous iteration

        # TODO: This is a buggggg..... we must fix the entire coordinate system? change init heading to 0
        self.heading = 180
        # TODO: change to custom type or enum
        self.output_state = "stopped"
        self.desired_heading = None
        self.angle_trace = None
        self.des_angle_trace = None
        self.oldError = 0
        self.errorHistory = 0
        self.mean = random.randint(-1, 1) / 12.0
        self.standard_deviation = random.randint(0, 1.0) / 10.0
        self.pid = None
        self.counter = 0

        self.prev_draw_c1c0_ids = [
            None, None
        ]  # previous IDs representing drawing of C1C0 on screen

    def create_widgets(self, empty=True):
        """Creates the canvas of the size of the inputted grid

        Create left side GUI with all obstacles visible
        If empty=True, draw empty grid (where robot doesn't know its surroundings)
        (function is run only at initialization of grid)
        """
        self.master.geometry("+900+100")
        if empty:
            map = self.gridEmpty.grid
        else:
            map = self.gridFull.grid
        width = len(map[0]) * GUI_tile_size
        height = len(map) * GUI_tile_size
        visMap = Canvas(self.master, width=width, height=height)
        offset = GUI_tile_size / 2
        if empty:
            tile_dict = {}
        for row in map:
            for tile in row:
                x = tile.x / tile_scale_fac
                y = tile.y / tile_scale_fac
                x1 = x - offset
                y1 = y - offset
                x2 = x + offset
                y2 = y + offset
                if tile.is_bloated:
                    color = "#ffc0cb"
                elif tile.is_obstacle:
                    color = "#ffCC99"
                else:
                    color = "#545454"
                if empty:
                    tile_dict[tile] = visMap.create_rectangle(x1,
                                                              y1,
                                                              x2,
                                                              y2,
                                                              outline=color,
                                                              fill=color)
        visMap.pack()
        self.canvas = visMap
        if (empty):
            self.tile_dict = tile_dict

    def calcVector(self):
        """
        Returns the vector between the current location and the end point of the current line segment
        and draws this vector onto the canvas
        """
        vect = (0, 0)
        if self.pathIndex + 1 < len(self.path):
            vect = self.pid.newVec()
            # print(vect)
            if self.prev_vector is not None:
                # delete old drawings from previous iteration
                self.canvas.delete(self.prev_vector)

            mag = (vect[0]**2 + vect[1]**2)**(1 / 2)
            norm_vect = (int(vector_draw_length * (vect[0] / mag)),
                         int(vector_draw_length * (vect[1] / mag)))
            end = self._scale_coords(
                (self.curr_x + norm_vect[0], self.curr_y + norm_vect[1]))
            start = self._scale_coords((self.curr_x, self.curr_y))
            self.prev_vector = self.canvas.create_line(start[0],
                                                       start[1],
                                                       end[0],
                                                       end[1],
                                                       arrow='last',
                                                       fill='red')
            # self.canvas.tag_raise(self.prev_vector)
        return vect

    def visibilityDraw(self, lidar_data):
        """Draws a circle of visibility around the robot
       """
        # coloring all tiles that were seen in last iteration light gray
        while self.last_iter_seen:
            curr_rec = self.last_iter_seen.pop()
            self.canvas.itemconfig(curr_rec, outline="#C7C7C7",
                                   fill="#C7C7C7")  # light gray

        row = self.curr_tile.row
        col = self.curr_tile.col
        index_radius_inner = int(vis_radius / tile_size)
        index_radius_outer = index_radius_inner + 2

        # the bounds for the visibility circle
        lower_row = max(0, row - index_radius_outer)
        lower_col = max(0, col - index_radius_outer)
        upper_row = min(row + index_radius_outer, self.gridFull.num_rows - 1)
        upper_col = min(col + index_radius_outer, self.gridFull.num_cols - 1)

        lidar_data_copy = copy.copy(lidar_data)
        rad_inc = int(GUI_tile_size / 3)  # radius increment to traverse tiles

        def _color_normally(r, angle_rad):
            """
           Colors the tile at the location that is at a distance r at heading angle_rad from robot's current location.
           Colors the tile based on its known attribute (obstacle, bloated, visited, or visible).
           """
            coords = (r * math.sin(angle_rad) + row,
                      r * math.cos(angle_rad) + col
                      )  # (row, col) of tile we want to color
            # make sure coords are in bounds of GUI window
            if (coords[0] >= lower_row) and (coords[0] <= upper_row) \
                    and (coords[1] >= lower_col) and (coords[1] <= upper_col):
                curr_tile = self.gridEmpty.grid[int(coords[0])][int(coords[1])]
                curr_rec = self.tile_dict[curr_tile]
                if curr_tile.is_bloated:
                    if 11 > curr_tile.bloat_score > 0:
                        color = bloat_colors[curr_tile.bloat_score]
                        self.canvas.itemconfig(curr_rec,
                                               outline=color,
                                               fill=color)  # blue
                        if curr_tile.bloat_score >= 11:
                            self.canvas.itemconfig(curr_rec,
                                                   outline="#000000",
                                                   fill="#000000")  # black
                elif curr_tile.is_obstacle:
                    # avg = math.ceil(sum(curr_tile.obstacle_score)/len(curr_tile.obstacle_score))
                    if curr_tile.obstacle_score[3] < 6:
                        color_pos = round(
                            len(obstacle_colors) / obstacle_threshold *
                            curr_tile.obstacle_score[3]) - 1
                        color = obstacle_colors[max(color_pos, 1)]
                        self.canvas.itemconfig(curr_rec,
                                               outline=color,
                                               fill=color)  # yellow
                    else:
                        self.canvas.itemconfig(curr_rec,
                                               outline="#cc8400",
                                               fill="#cc8400")  # darker yellow
                else:
                    self.canvas.itemconfig(curr_rec,
                                           outline="#fff",
                                           fill="#fff")  # white
                    self.last_iter_seen.add(curr_rec)

        # iterating through 360 degrees surroundings of robot in increments of degree_freq
        for deg in range(0, 360, degree_freq):
            angle_rad = deg * math.pi / 180
            if len(lidar_data_copy) == 0 or lidar_data_copy[0][0] != deg:
                # no object in sight at deg; color everything normally up to visibility radius
                for r in range(0, index_radius_inner, rad_inc):
                    _color_normally(r, angle_rad)
            else:  # obstacle in sight
                # color everything normally UP TO obstacle, and color obstacle red
                for r in range(
                        0,
                        math.ceil(lidar_data_copy[0][1] / tile_size) + rad_inc,
                        rad_inc):
                    _color_normally(r, angle_rad)
                lidar_data_copy.pop(0)

    def _scale_coords(self, coords):
        """scales coords (a tuple (x, y)) from real life cm to pixels"""
        scaled_x = coords[0] / tile_scale_fac
        scaled_y = coords[1] / tile_scale_fac
        return scaled_x, scaled_y

    def drawC1C0(self):
        """Draws C1C0's current location on the simulation"""

        # coordinates of robot center right now (in cm)
        center_x = self.curr_x
        center_y = self.curr_y

        # converting heading to radians, and adjusting so that facing right = 0 deg
        heading_adj_rad = math.radians(self.heading + 90)

        if self.prev_draw_c1c0_ids is not None:
            # delete old drawings from previous iteration
            self.canvas.delete(self.prev_draw_c1c0_ids[0])
            self.canvas.delete(self.prev_draw_c1c0_ids[1])
        # coordinates of bounding square around blue circle
        top_left_coords = (center_x - robot_radius, center_y + robot_radius)
        bot_right_coords = (center_x + robot_radius, center_y - robot_radius)
        # convert coordinates from cm to pixels
        top_left_coords_scaled = self._scale_coords(top_left_coords)
        bot_right_coords_scaled = self._scale_coords(bot_right_coords)

        # draw blue circle
        self.prev_draw_c1c0_ids[0] = self.canvas.create_oval(
            top_left_coords_scaled[0],
            top_left_coords_scaled[1],
            bot_right_coords_scaled[0],
            bot_right_coords_scaled[1],
            outline='black',
            fill='blue')

        center_coords_scaled = self._scale_coords((center_x, center_y))

        # finding endpoint coords of arrow
        arrow_end_x = center_x + robot_radius * math.cos(heading_adj_rad)
        arrow_end_y = center_y + robot_radius * math.sin(heading_adj_rad)
        arrow_end_coords_scaled = self._scale_coords(
            (arrow_end_x, arrow_end_y))

        # draw white arrow
        self.prev_draw_c1c0_ids[1] = self.canvas.create_line(
            center_coords_scaled[0],
            center_coords_scaled[1],
            arrow_end_coords_scaled[0],
            arrow_end_coords_scaled[1],
            arrow='last',
            fill='white')

    def generatePathSet(self):
        self.pathSet = set()
        for i in range(len(self.path) - 1):
            self.breakUpLine(self.path[i], self.path[i + 1])

    def breakUpLine(self, curr_tile, next_tile):
        current_loc = (curr_tile.x, curr_tile.y)
        next_loc = (next_tile.x, next_tile.y)
        # calculate the slope, rise/run
        x_change = next_loc[0] - current_loc[0]
        y_change = next_loc[1] - current_loc[1]
        dist = math.sqrt(x_change**2 + y_change**2)

        # if (dist < tile_size):
        #     return [(current_loc[0] + x_change, current_loc[1] + y_change)]

        num_steps = int(dist / tile_size)
        returner = []

        if y_change == 0:
            x_step = tile_size
            y_step = 0
        elif x_change == 0:
            x_step = 0
            y_step = tile_size
        else:
            inv_slope = x_change / y_change
            # x^2+y^2 = tile_size^2    &&     x/y = x_change/y_change
            y_step = math.sqrt(tile_size**2 / (inv_slope**2 + 1))
            y_step = y_step
            x_step = math.sqrt(
                (tile_size**2 * inv_slope**2) / (inv_slope**2 + 1))
            x_step = x_step
        if x_change < 0 and x_step > 0:
            x_step = -x_step
        if y_change < 0 and y_step:
            y_step = -y_step

        for i in range(1, num_steps + 1):
            new_coor = (current_loc[0] + i * x_step,
                        current_loc[1] + i * y_step)
            returner.append(new_coor)
            new_tile = self.gridEmpty.get_tile(new_coor)
            if new_tile not in self.pathSet:
                self.pathSet.add(new_tile)

    def printTiles(self):
        for row in self.gridEmpty.grid:
            for col in row:
                print(str(col.x) + ', ' + str(col.y))

    def updateDesiredHeading(self, next_x=None, next_y=None):
        """
        calculates the degrees between the current tile and the next tile and updates desired_heading. Estimates the
        degrees to the nearing int.
        """
        next_x = self.next_tile.x if next_x is None else next_x
        next_y = self.next_tile.y if next_y is None else next_y
        x_change = next_x - self.curr_x
        y_change = next_y - self.curr_y
        if y_change == 0:
            arctan = 90 if x_change < 0 else -90
        else:
            arctan = math.atan(x_change / y_change) * (180 / math.pi)
        if x_change >= 0 and y_change > 0:
            self.desired_heading = (360 - arctan) % 360
        elif x_change < 0 and y_change > 0:
            self.desired_heading = -arctan
        else:
            self.desired_heading = 180 - arctan
        self.desired_heading = round(self.desired_heading)
        # print("updated desired heading to : " + str(self.desired_heading))

    def draw_line(self, curr_x, curr_y, next_x, next_y):
        """
        Draw a line from the coordinate (curr_x, curr_y) to the coordinate (next_x, next_y)
        """
        self.canvas.create_line(curr_x / tile_scale_fac,
                                curr_y / tile_scale_fac,
                                next_x / tile_scale_fac,
                                next_y / tile_scale_fac,
                                fill="#339933",
                                width=1.5)

    def init_phase(self):
        curr_tile = self.path[0]
        self.prev_tile = self.curr_tile
        self.curr_tile = curr_tile
        self.curr_x = self.curr_tile.x
        self.curr_y = self.curr_tile.y
        self.prev_x = self.curr_tile.x
        self.prev_y = self.curr_tile.y

        self.visitedSet.add(curr_tile)
        self.generatePathSet()
        lidar_data = self.generate_sensor.generateLidar(
            degree_freq, curr_tile.row, curr_tile.col)
        self.generatePathSet()
        self.recalc = self.gridEmpty.update_grid_tup_data(
            curr_tile.x, curr_tile.y, lidar_data, Tile.lidar, robot_radius,
            bloat_factor, self.pathSet)
        self.next_tile = self.path[1]
        self.visibilityDraw(lidar_data)
        self.updateDesiredHeading()
        self.pid = PID(self.path, self.pathIndex + 1, self.curr_x, self.curr_y)

        self.master.after(fast_speed_dynamic, self.main_loop)

    def turn(self):
        while self.desired_heading is not None and self.heading != self.desired_heading:
            self.drawC1C0()
            if self.heading < self.desired_heading:
                cw_turn_degrees = 360 + self.heading - self.desired_heading
                ccw_turn_degrees = self.desired_heading - self.heading
            else:
                cw_turn_degrees = self.heading - self.desired_heading
                ccw_turn_degrees = 360 - self.heading + self.desired_heading
            if abs(self.desired_heading - self.heading) < turn_speed:
                self.heading = self.desired_heading
            else:
                if cw_turn_degrees < ccw_turn_degrees:  # turn clockwise
                    self.heading = self.heading - turn_speed
                    self.output_state = "turn right"
                else:  # turn counter clockwise
                    self.heading = self.heading + turn_speed
                    self.output_state = "turn left"
            if self.heading < 0:
                self.heading = 360 + self.heading
            elif self.heading >= 360:
                self.heading = self.heading - 360

    def recalculate_path(self, lidar_data):
        self.path = search.a_star_search(self.gridEmpty,
                                         (self.curr_x, self.curr_y),
                                         self.endPoint, search.euclidean)
        self.path = search.segment_path(self.gridEmpty, self.path)
        self.pathIndex = 0
        self.smoothed_window.path = self.path
        self.smoothed_window.drawPath()
        self.draw_line(self.curr_x, self.curr_y, self.path[0].x,
                       self.path[0].y)
        self.prev_tile = self.curr_tile
        self.curr_tile = self.path[0]
        self.curr_x = self.curr_tile.x
        self.curr_y = self.curr_tile.y
        self.next_tile = self.path[1]
        self.updateDesiredHeading()
        self.generatePathSet()
        self.visibilityDraw(lidar_data)
        self.pathIndex = 0
        self.recalc = False
        self.recalc_count = 0
        self.recalc_cond = False
        self.drawWayPoint(self.next_tile)
        self.pid = PID(self.path, self.pathIndex + 1, self.curr_x, self.curr_y)

    def get_next_pos(self, vec, lidar_data):
        mag = math.sqrt(vec[0]**2 + vec[1]**2)
        error = np.random.normal(self.mean, self.standard_deviation)
        # error = 0
        norm_vec = (20 * vec[0] / mag, 20 * vec[1] / mag)
        check_tile = self.gridEmpty.get_tile(
            (self.curr_x + norm_vec[0], self.curr_y + norm_vec[1]))
        if check_tile.is_obstacle and not check_tile.is_bloated:
            print('had to recalculate :(')
            self.recalculate_path(lidar_data)
        norm_vec = (norm_vec[0] * math.cos(error) -
                    norm_vec[1] * math.sin(error),
                    norm_vec[0] * math.sin(error) +
                    norm_vec[1] * math.cos(error))
        x2 = self.curr_x + norm_vec[0]
        y2 = self.curr_y + norm_vec[1]

        return x2, y2

    def step(self, lidar_data, vec=None):
        """
        steps in the direction of the vector
        """
        if vec is None:
            vec = self.calcVector()

        x2, y2 = self.get_next_pos(vec, lidar_data)

        self.draw_line(self.curr_x, self.curr_y, x2, y2)
        self.prev_x = self.curr_x
        self.prev_y = self.curr_y
        self.curr_x = x2
        self.curr_y = y2
        self.pid.update_PID(self.curr_x, self.curr_y)
        self.prev_tile = self.curr_tile
        self.curr_tile = self.gridEmpty.get_tile((x2, y2))
        self.visitedSet.add(self.curr_tile)
        self.visibilityDraw(lidar_data)
        self.drawC1C0()
        self.recalc_count += 1

    def emergency_step(self, lidar_data):
        """
        Find the nearest obstacle to the
        """
        min_obs_dist = float('inf')
        min_obs: Tile = None
        max_dist = int(bloat_factor * robot_radius)
        step_dist = int(tile_size / 2)

        for ang_deg in range(0, 360, 5):
            for dist in range(0, max_dist, step_dist):
                ang_rad = math.radians(ang_deg)
                x2 = self.curr_x + dist * math.cos(math.radians(ang_rad))
                y2 = self.curr_y + dist * math.cos(math.radians(ang_rad))
                curr_tile = self.gridEmpty.get_tile((x2, y2))
                if curr_tile.is_obstacle and not curr_tile.is_bloated:
                    break
            if dist < min_obs_dist:
                min_obs_dist = dist
                min_obs = curr_tile

        vec = (-self.curr_x + min_obs.x, -self.curr_y + min_obs.y)
        x2, y2 = self.get_next_pos(vec, lidar_data)
        self.updateDesiredHeading(x2, y2)
        self.step(lidar_data, vec)

    def main_loop(self):
        """
        updates the grid in a smoothed fashion
        """
        if self.curr_tile == self.gridEmpty.get_tile(self.endPoint):
            print('C1C0 has ARRIVED AT THE DESTINATION, destination tile')
            return
        lidar_data = self.generate_sensor.generateLidar(
            degree_freq, self.curr_tile.row, self.curr_tile.col)
        self.recalc = self.gridEmpty.update_grid_tup_data(
            self.curr_x, self.curr_y, lidar_data, Tile.lidar, robot_radius,
            bloat_factor, self.pathSet)
        self.turn()
        self.recalc_cond = self.recalc_cond or self.recalc

        # if (self.curr_tile.is_obstacle and self.curr_tile.bloat_score > 6) or \
        #         (self.curr_tile.is_obstacle and not self.curr_tile.is_bloated):
        #     self.emergency_step(lidar_data)
        #     self.recalc_cond = True
        # else:
        # if we need to recalculate then recurse
        if self.recalc_cond and self.recalc_count >= recalc_wait:
            try:
                self.recalculate_path(lidar_data)
            except Exception as e:
                print('error occured, ', e)
                self.emergency_step(lidar_data)
                self.recalc_cond = True
        elif self.nextLoc():
            self.mean = random.randint(-1, 1) / 12.0
            self.standard_deviation = random.randint(0, 1) / 10.0
            self.pathIndex += 1
            self.next_tile = self.path[self.pathIndex]
            self.drawWayPoint(self.next_tile)
            self.updateDesiredHeading()
            self.pid = PID(self.path, self.pathIndex + 1, self.curr_x,
                           self.curr_y)
        else:
            #print(self.path[self.pathIndex].row, self.path[self.pathIndex].col)
            #print(self.path[self.pathIndex+1].row, self.path[self.pathIndex+1].col)
            #print(self.curr_tile.is_bloated)
            self.step(lidar_data)
        self.drawC1C0()
        self.master.after(fast_speed_dynamic, self.main_loop)

    def nextLoc(self):
        # (xp−xc)2+(yp−yc)2 with r2. (xp−xc)2+(yp−yc)2 with r2.
        d = math.sqrt((self.curr_x - self.next_tile.x)**2 +
                      (self.curr_y - self.next_tile.y)**2)
        # print(d)
        return d <= reached_tile_bound

    def runSimulation(self):
        """Runs a sumulation of this map, with its enviroment and path
        """
        self.smoothed_window = StaticGUI.SmoothedPathGUI(
            Toplevel(), self.gridFull, self.path)
        self.smoothed_window.drawPath()
        self.init_phase()
        self.master.mainloop()

    def drawWayPoint(self, new_tile):
        if self.way_point is not None:
            self.canvas.delete(self.way_point)
        offset = GUI_tile_size
        x = new_tile.x / tile_scale_fac
        y = new_tile.y / tile_scale_fac
        self.way_point = self.canvas.create_oval(x - offset,
                                                 y - offset,
                                                 x + offset,
                                                 y + offset,
                                                 outline="#FF0000",
                                                 fill="#FF0000")