From 8e8d82824f0e3f94a5c1c26f3e6db006a7d59c48 Mon Sep 17 00:00:00 2001 From: Akshay Naik <akshaynaik@Akshays-MacBook-Air.local> Date: Thu, 15 Feb 2024 22:34:45 -0600 Subject: [PATCH] Added mp2 source code --- src/mp2/CMakeLists.txt | 207 +++++++++++++++++++++++++++++++++++ src/mp2/launch/mp2.launch | 32 ++++++ src/mp2/package.xml | 71 ++++++++++++ src/mp2/src/controller.py | 103 +++++++++++++++++ src/mp2/src/main.py | 74 +++++++++++++ src/mp2/src/set_pos.py | 76 +++++++++++++ src/mp2/src/util.py | 22 ++++ src/mp2/src/waypoint_list.py | 124 +++++++++++++++++++++ 8 files changed, 709 insertions(+) create mode 100644 src/mp2/CMakeLists.txt create mode 100644 src/mp2/launch/mp2.launch create mode 100644 src/mp2/package.xml create mode 100644 src/mp2/src/controller.py create mode 100644 src/mp2/src/main.py create mode 100644 src/mp2/src/set_pos.py create mode 100644 src/mp2/src/util.py create mode 100644 src/mp2/src/waypoint_list.py diff --git a/src/mp2/CMakeLists.txt b/src/mp2/CMakeLists.txt new file mode 100644 index 0000000..9fa6226 --- /dev/null +++ b/src/mp2/CMakeLists.txt @@ -0,0 +1,207 @@ +cmake_minimum_required(VERSION 3.0.2) +project(mp2) + +## Compile as C++11, supported in ROS Kinetic and newer +# add_compile_options(-std=c++11) + +## Find catkin macros and libraries +## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) +## is used, also find other catkin packages +find_package(catkin REQUIRED COMPONENTS + gazebo_ros + roscpp + rospy + std_msgs +) + +## System dependencies are found with CMake's conventions +# find_package(Boost REQUIRED COMPONENTS system) + + +## Uncomment this if the package has a setup.py. This macro ensures +## modules and global scripts declared therein get installed +## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html +# catkin_python_setup() + +################################################ +## Declare ROS messages, services and actions ## +################################################ + +## To declare and build messages, services or actions from within this +## package, follow these steps: +## * Let MSG_DEP_SET be the set of packages whose message types you use in +## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). +## * In the file package.xml: +## * add a build_depend tag for "message_generation" +## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET +## * If MSG_DEP_SET isn't empty the following dependency has been pulled in +## but can be declared for certainty nonetheless: +## * add a exec_depend tag for "message_runtime" +## * In this file (CMakeLists.txt): +## * add "message_generation" and every package in MSG_DEP_SET to +## find_package(catkin REQUIRED COMPONENTS ...) +## * add "message_runtime" and every package in MSG_DEP_SET to +## catkin_package(CATKIN_DEPENDS ...) +## * uncomment the add_*_files sections below as needed +## and list every .msg/.srv/.action file to be processed +## * uncomment the generate_messages entry below +## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) + +## Generate messages in the 'msg' folder +# add_message_files( +# FILES +# Message1.msg +# Message2.msg +# ) + +## Generate services in the 'srv' folder +# add_service_files( +# FILES +# Service1.srv +# Service2.srv +# ) + +## Generate actions in the 'action' folder +# add_action_files( +# FILES +# Action1.action +# Action2.action +# ) + +## Generate added messages and services with any dependencies listed here +# generate_messages( +# DEPENDENCIES +# std_msgs +# ) + +################################################ +## Declare ROS dynamic reconfigure parameters ## +################################################ + +## To declare and build dynamic reconfigure parameters within this +## package, follow these steps: +## * In the file package.xml: +## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" +## * In this file (CMakeLists.txt): +## * add "dynamic_reconfigure" to +## find_package(catkin REQUIRED COMPONENTS ...) +## * uncomment the "generate_dynamic_reconfigure_options" section below +## and list every .cfg file to be processed + +## Generate dynamic reconfigure parameters in the 'cfg' folder +# generate_dynamic_reconfigure_options( +# cfg/DynReconf1.cfg +# cfg/DynReconf2.cfg +# ) + +################################### +## catkin specific configuration ## +################################### +## The catkin_package macro generates cmake config files for your package +## Declare things to be passed to dependent projects +## INCLUDE_DIRS: uncomment this if your package contains header files +## LIBRARIES: libraries you create in this project that dependent projects also need +## CATKIN_DEPENDS: catkin_packages dependent projects also need +## DEPENDS: system dependencies of this project that dependent projects also need +catkin_package( +# INCLUDE_DIRS include +# LIBRARIES mp2 +# CATKIN_DEPENDS gazebo_ros roscp rospy std_msgs +# DEPENDS system_lib +) + +########### +## Build ## +########### + +## Specify additional locations of header files +## Your package locations should be listed before other locations +include_directories( +# include + ${catkin_INCLUDE_DIRS} +) + +## Declare a C++ library +# add_library(${PROJECT_NAME} +# src/${PROJECT_NAME}/mp2.cpp +# ) + +## Add cmake target dependencies of the library +## as an example, code may need to be generated before libraries +## either from message generation or dynamic reconfigure +# add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Declare a C++ executable +## With catkin_make all packages are built within a single CMake context +## The recommended prefix ensures that target names across packages don't collide +# add_executable(${PROJECT_NAME}_node src/mp2_node.cpp) + +## Rename C++ executable without prefix +## The above recommended prefix causes long target names, the following renames the +## target back to the shorter version for ease of user use +## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" +# set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") + +## Add cmake target dependencies of the executable +## same as for the library above +# add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) + +## Specify libraries to link a library or executable target against +# target_link_libraries(${PROJECT_NAME}_node +# ${catkin_LIBRARIES} +# ) + +############# +## Install ## +############# + +# all install targets should use catkin DESTINATION variables +# See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html + +## Mark executable scripts (Python etc.) for installation +## in contrast to setup.py, you can choose the destination +# catkin_install_python(PROGRAMS +# scripts/my_python_script +# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark executables for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html +# install(TARGETS ${PROJECT_NAME}_node +# RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark libraries for installation +## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html +# install(TARGETS ${PROJECT_NAME} +# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} +# ) + +## Mark cpp header files for installation +# install(DIRECTORY include/${PROJECT_NAME}/ +# DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} +# FILES_MATCHING PATTERN "*.h" +# PATTERN ".svn" EXCLUDE +# ) + +## Mark other files for installation (e.g. launch and bag files, etc.) +# install(FILES +# # myfile1 +# # myfile2 +# DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} +# ) + +############# +## Testing ## +############# + +## Add gtest based cpp test target and link libraries +# catkin_add_gtest(${PROJECT_NAME}-test test/test_mp2.cpp) +# if(TARGET ${PROJECT_NAME}-test) +# target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) +# endif() + +## Add folders to be run by python nosetests +# catkin_add_nosetests(test) diff --git a/src/mp2/launch/mp2.launch b/src/mp2/launch/mp2.launch new file mode 100644 index 0000000..af693b3 --- /dev/null +++ b/src/mp2/launch/mp2.launch @@ -0,0 +1,32 @@ +<?xml version="1.0"?> + +<launch> + <arg name="paused" default="false"/> + <arg name="use_sim_time" default="true"/> + <arg name="gui" default="true"/> + <arg name="headless" default="false"/> + <arg name="debug" default="false"/> + + <include file="$(find gazebo_ros)/launch/empty_world.launch"> + <arg name="world_name" value="$(find gem_gazebo)/worlds/smaller_track_with_starting_point.world"/> + <arg name="debug" value="$(arg debug)" /> + <arg name="gui" value="$(arg gui)" /> + <arg name="paused" value="$(arg paused)"/> + <arg name="use_sim_time" value="$(arg use_sim_time)"/> + <arg name="headless" value="$(arg headless)"/> + <arg name="verbose" value="false"/> + </include> + + + <!-- Spawn the GEM CAR --> + <include file="$(find gem_gazebo)/launch/gem_vehicle.launch"> + <!-- <arg name="namespace" value="/gem"/> --> + <arg name="x" value="0.0"/> + <arg name="y" value="-98"/> + <arg name="z" value="1.0"/> + </include> + + <!-- RViz --> + <node name="rviz" pkg="rviz" type="rviz" args="-d $(find gem_description)/config_rviz/gem_velodyne.rviz" /> + +</launch> diff --git a/src/mp2/package.xml b/src/mp2/package.xml new file mode 100644 index 0000000..918e9a5 --- /dev/null +++ b/src/mp2/package.xml @@ -0,0 +1,71 @@ +<?xml version="1.0"?> +<package format="2"> + <name>mp2</name> + <version>0.0.0</version> + <description>The mp2 package</description> + + <!-- One maintainer tag required, multiple allowed, one person per tag --> + <!-- Example: --> + <!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> --> + <maintainer email="pricejiang@todo.todo">pricejiang</maintainer> + + + <!-- One license tag required, multiple allowed, one license per tag --> + <!-- Commonly used license strings: --> + <!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 --> + <license>TODO</license> + + + <!-- Url tags are optional, but multiple are allowed, one per tag --> + <!-- Optional attribute type can be: website, bugtracker, or repository --> + <!-- Example: --> + <!-- <url type="website">http://wiki.ros.org/mp2</url> --> + + + <!-- Author tags are optional, multiple are allowed, one per tag --> + <!-- Authors do not have to be maintainers, but could be --> + <!-- Example: --> + <!-- <author email="jane.doe@example.com">Jane Doe</author> --> + + + <!-- The *depend tags are used to specify dependencies --> + <!-- Dependencies can be catkin packages or system dependencies --> + <!-- Examples: --> + <!-- Use depend as a shortcut for packages that are both build and exec dependencies --> + <!-- <depend>roscpp</depend> --> + <!-- Note that this is equivalent to the following: --> + <!-- <build_depend>roscpp</build_depend> --> + <!-- <exec_depend>roscpp</exec_depend> --> + <!-- Use build_depend for packages you need at compile time: --> + <!-- <build_depend>message_generation</build_depend> --> + <!-- Use build_export_depend for packages you need in order to build against this package: --> + <!-- <build_export_depend>message_generation</build_export_depend> --> + <!-- Use buildtool_depend for build tool packages: --> + <!-- <buildtool_depend>catkin</buildtool_depend> --> + <!-- Use exec_depend for packages you need at runtime: --> + <!-- <exec_depend>message_runtime</exec_depend> --> + <!-- Use test_depend for packages you need only for testing: --> + <!-- <test_depend>gtest</test_depend> --> + <!-- Use doc_depend for packages you need only for building documentation: --> + <!-- <doc_depend>doxygen</doc_depend> --> + <buildtool_depend>catkin</buildtool_depend> + <build_depend>gazebo_ros</build_depend> + <build_depend>roscp</build_depend> + <build_depend>rospy</build_depend> + <build_depend>std_msgs</build_depend> + <build_export_depend>gazebo_ros</build_export_depend> + <build_export_depend>roscp</build_export_depend> + <build_export_depend>rospy</build_export_depend> + <build_export_depend>std_msgs</build_export_depend> + <exec_depend>gazebo_ros</exec_depend> + <exec_depend>roscp</exec_depend> + <exec_depend>rospy</exec_depend> + <exec_depend>std_msgs</exec_depend> + + + <!-- The export tag contains other, unspecified, tags --> + <export> + <!-- Other tools can request additional information be placed here --> + + </export> +</package> diff --git a/src/mp2/src/controller.py b/src/mp2/src/controller.py new file mode 100644 index 0000000..e959a48 --- /dev/null +++ b/src/mp2/src/controller.py @@ -0,0 +1,103 @@ +import rospy +from gazebo_msgs.srv import GetModelState, GetModelStateResponse +from gazebo_msgs.msg import ModelState +from ackermann_msgs.msg import AckermannDrive +import numpy as np +from std_msgs.msg import Float32MultiArray +import math +from util import euler_to_quaternion, quaternion_to_euler +import time + +class vehicleController(): + + def __init__(self): + # Publisher to publish the control input to the vehicle model + self.controlPub = rospy.Publisher("/ackermann_cmd", AckermannDrive, queue_size = 1) + self.prev_vel = 0 + self.L = 1.75 # Wheelbase, can be get from gem_control.py + self.log_acceleration = False + + def getModelState(self): + # Get the current state of the vehicle + # Input: None + # Output: ModelState, the state of the vehicle, contain the + # position, orientation, linear velocity, angular velocity + # of the vehicle + rospy.wait_for_service('/gazebo/get_model_state') + try: + serviceResponse = rospy.ServiceProxy('/gazebo/get_model_state', GetModelState) + resp = serviceResponse(model_name='gem') + except rospy.ServiceException as exc: + rospy.loginfo("Service did not process request: "+str(exc)) + resp = GetModelStateResponse() + resp.success = False + return resp + + + # Tasks 1: Read the documentation https://docs.ros.org/en/fuerte/api/gazebo/html/msg/ModelState.html + # and extract yaw, velocity, vehicle_position_x, vehicle_position_y + # Hint: you may use the the helper function(quaternion_to_euler()) we provide to convert from quaternion to euler + def extract_vehicle_info(self, currentPose): + + ####################### TODO: Your TASK 1 code starts Here ####################### + pos_x, pos_y, vel, yaw = 0, 0, 0, 0 + + ####################### TODO: Your Task 1 code ends Here ####################### + + return pos_x, pos_y, vel, yaw # note that yaw is in radian + + # Task 2: Longtitudal Controller + # Based on all unreached waypoints, and your current vehicle state, decide your velocity + def longititudal_controller(self, curr_x, curr_y, curr_vel, curr_yaw, future_unreached_waypoints): + + ####################### TODO: Your TASK 2 code starts Here ####################### + target_velocity = 10 + + + ####################### TODO: Your TASK 2 code ends Here ####################### + return target_velocity + + + # Task 3: Lateral Controller (Pure Pursuit) + def pure_pursuit_lateral_controller(self, curr_x, curr_y, curr_yaw, target_point, future_unreached_waypoints): + + ####################### TODO: Your TASK 3 code starts Here ####################### + target_steering = 0 + + ####################### TODO: Your TASK 3 code starts Here ####################### + return target_steering + + + def execute(self, currentPose, target_point, future_unreached_waypoints): + # Compute the control input to the vehicle according to the + # current and reference pose of the vehicle + # Input: + # currentPose: ModelState, the current state of the vehicle + # target_point: [target_x, target_y] + # future_unreached_waypoints: a list of future waypoints[[target_x, target_y]] + # Output: None + + curr_x, curr_y, curr_vel, curr_yaw = self.extract_vehicle_info(currentPose) + + # Acceleration Profile + if self.log_acceleration: + acceleration = (curr_vel- self.prev_vel) * 100 # Since we are running in 100Hz + + + + target_velocity = self.longititudal_controller(curr_x, curr_y, curr_vel, curr_yaw, future_unreached_waypoints) + target_steering = self.pure_pursuit_lateral_controller(curr_x, curr_y, curr_yaw, target_point, future_unreached_waypoints) + + + #Pack computed velocity and steering angle into Ackermann command + newAckermannCmd = AckermannDrive() + newAckermannCmd.speed = target_velocity + newAckermannCmd.steering_angle = target_steering + + # Publish the computed control input to vehicle model + self.controlPub.publish(newAckermannCmd) + + def stop(self): + newAckermannCmd = AckermannDrive() + newAckermannCmd.speed = 0 + self.controlPub.publish(newAckermannCmd) diff --git a/src/mp2/src/main.py b/src/mp2/src/main.py new file mode 100644 index 0000000..45acc54 --- /dev/null +++ b/src/mp2/src/main.py @@ -0,0 +1,74 @@ +import rospy +import numpy as np +import argparse + +from gazebo_msgs.msg import ModelState +from controller import vehicleController +import time +from waypoint_list import WayPoints +from util import euler_to_quaternion, quaternion_to_euler + +def run_model(): + rospy.init_node("model_dynamics") + controller = vehicleController() + + waypoints = WayPoints() + pos_list = waypoints.getWayPoints() + pos_idx = 1 + + target_x, target_y = pos_list[pos_idx] + + def shutdown(): + """Stop the car when this ROS node shuts down""" + controller.stop() + rospy.loginfo("Stop the car") + + rospy.on_shutdown(shutdown) + + rate = rospy.Rate(100) # 100 Hz + rospy.sleep(0.0) + start_time = rospy.Time.now() + prev_wp_time = start_time + + while not rospy.is_shutdown(): + rate.sleep() # Wait a while before trying to get a new state + + # Get the current position and orientation of the vehicle + currState = controller.getModelState() + + if not currState.success: + continue + + # Compute relative position between vehicle and waypoints + distToTargetX = abs(target_x - currState.pose.position.x) + distToTargetY = abs(target_y - currState.pose.position.y) + + cur_time = rospy.Time.now() + if (cur_time - prev_wp_time).to_sec() > 4: + print(f"failure to reach {pos_idx}-th waypoint in time") + return False, pos_idx, (cur_time - start_time).to_sec() + + if (distToTargetX < 2 and distToTargetY < 2): + # If the vehicle is close to the waypoint, move to the next waypoint + prev_pos_idx = pos_idx + pos_idx = pos_idx+1 + + if pos_idx == len(pos_list): #Reached all the waypoints + print("Reached all the waypoints") + total_time = (cur_time - start_time).to_sec() + print(total_time) + return True, pos_idx, total_time + + target_x, target_y = pos_list[pos_idx] + + time_taken = (cur_time- prev_wp_time).to_sec() + prev_wp_time = cur_time + print(f"Time Taken: {round(time_taken, 2)}", "reached",pos_list[prev_pos_idx][0],pos_list[prev_pos_idx][1],"next",pos_list[pos_idx][0],pos_list[pos_idx][1]) + + controller.execute(currState, [target_x, target_y], pos_list[pos_idx:]) + +if __name__ == "__main__": + try: + status, num_waypoints, time_taken = run_model() + except rospy.exceptions.ROSInterruptException: + rospy.loginfo("Shutting down") diff --git a/src/mp2/src/set_pos.py b/src/mp2/src/set_pos.py new file mode 100644 index 0000000..f7806ce --- /dev/null +++ b/src/mp2/src/set_pos.py @@ -0,0 +1,76 @@ +import sys +import os +import argparse + +import numpy as np + +import rospy +from gazebo_msgs.msg import ModelState +from gazebo_msgs.srv import SetModelState +from gazebo_msgs.srv import GetModelState + +from util import euler_to_quaternion + +def getModelState(): + rospy.wait_for_service('/gazebo/get_model_state') + try: + serviceResponse = rospy.ServiceProxy('/gazebo/get_model_state', GetModelState) + modelState = serviceResponse(model_name='gem') + except rospy.ServiceException as exc: + rospy.loginfo("Service did not process request: "+str(exc)) + return modelState + +def setModelState(model_state): + rospy.wait_for_service('/gazebo/set_model_state') + try: + set_state = rospy.ServiceProxy('/gazebo/set_model_state', SetModelState) + resp = set_state(model_state) + except rospy.ServiceException as e: + rospy.loginfo("Service did not process request: "+str(e)) + +def set_position(x = 0,y = 0, yaw=0): + + rospy.init_node("set_pos") + + curr_state = getModelState() + new_state = ModelState() + new_state.model_name = 'gem' + new_state.twist.linear.x = 0 + new_state.twist.linear.y = 0 + new_state.twist.linear.z = 0 + new_state.pose.position.x = x + new_state.pose.position.y = y + new_state.pose.position.z = 1 + q = euler_to_quaternion([0,0,yaw]) + new_state.pose.orientation.x = q[0] + new_state.pose.orientation.y = q[1] + new_state.pose.orientation.z = q[2] + new_state.pose.orientation.w = q[3] + new_state.twist.angular.x = 0 + new_state.twist.angular.y = 0 + new_state.twist.angular.z = 0 + setModelState(new_state) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description = 'Set the x, y position of the vehicle') + + x_default = 0 + y_default = -98 + yaw_default = 0 + + # x_default = 160 + # y_default = 96.5 + # yaw_default = 3.14 + + + parser.add_argument('--x', type = float, help = 'x position of the vehicle.', default = x_default) + parser.add_argument('--y', type = float, help = 'y position of the vehicle.', default = y_default) + parser.add_argument('--yaw', type = float, help = 'yaw of the vehicle.', default = yaw_default) + + argv = parser.parse_args() + + x = argv.x + y = argv.y + yaw = argv.yaw + + set_position(x = x, y = y, yaw = yaw) diff --git a/src/mp2/src/util.py b/src/mp2/src/util.py new file mode 100644 index 0000000..fcf54a9 --- /dev/null +++ b/src/mp2/src/util.py @@ -0,0 +1,22 @@ +import numpy as np + +def euler_to_quaternion(r): + (roll, pitch, yaw) = (r[0], r[1], r[2]) + qx = np.sin(roll/2) * np.cos(pitch/2) * np.cos(yaw/2) - np.cos(roll/2) * np.sin(pitch/2) * np.sin(yaw/2) + qy = np.cos(roll/2) * np.sin(pitch/2) * np.cos(yaw/2) + np.sin(roll/2) * np.cos(pitch/2) * np.sin(yaw/2) + qz = np.cos(roll/2) * np.cos(pitch/2) * np.sin(yaw/2) - np.sin(roll/2) * np.sin(pitch/2) * np.cos(yaw/2) + qw = np.cos(roll/2) * np.cos(pitch/2) * np.cos(yaw/2) + np.sin(roll/2) * np.sin(pitch/2) * np.sin(yaw/2) + return [qx, qy, qz, qw] + +def quaternion_to_euler(x, y, z, w): + t0 = +2.0 * (w * x + y * z) + t1 = +1.0 - 2.0 * (x * x + y * y) + roll = np.arctan2(t0, t1) + t2 = +2.0 * (w * y - z * x) + t2 = +1.0 if t2 > +1.0 else t2 + t2 = -1.0 if t2 < -1.0 else t2 + pitch = np.arcsin(t2) + t3 = +2.0 * (w * z + x * y) + t4 = +1.0 - 2.0 * (y * y + z * z) + yaw = np.arctan2(t3, t4) + return [roll, pitch, yaw] \ No newline at end of file diff --git a/src/mp2/src/waypoint_list.py b/src/mp2/src/waypoint_list.py new file mode 100644 index 0000000..931919e --- /dev/null +++ b/src/mp2/src/waypoint_list.py @@ -0,0 +1,124 @@ +import numpy as np + + +class WayPoints(): + def __init__(self): + self.pos_list = [ + [0,-98], # 0th wp + [10,-98], + [20,-98], + [30,-98], + [40,-98], + [50,-98], + [60,-98], + [70,-98], + [80,-98], + [90,-98], + [100,-98], # 10th + [110,-98], + [120,-98], + [130,-98], + [140,-98], + [150,-98], + [160,-98], + [170,-98], + [180,-98], + [190,-98], + [200,-98], # 20th + [210,-98], + [220,-97], + [230,-95], + [240,-92], + [250,-87], + [260,-81], + [270,-74], + [280,-63], + [290,-49], + [295,-37], # 30 th + [300,-22], + [303,-5], + [303,15], + [303,25], + [303,35], + [303,50], + [301,63], + [295,77], + [290,83], + [285,87], # 40th + [280,90], + [275,93], + [270,94.5], + [250,96.5], + [240,96.5], + [230,96.5], + [220,96.5], + [210,96.5], + [200,96.5], + [190,96.5], # 50th + [180,96.5], + [170,96.5], + [160,96.5], + [150,96.5], + [140,96.5], + [130,96.5], + [120,96.5], + [110,96.5], + [100,96.5], + [90,96.5], # 60th + [80,96.5], + [70,96.5], + [60,96.5], + [50,96.5], + [40,99], + [30,103], + [20,109], + [15,115], + [9,125], + [5,135], # 70th + [4,145], + [3,157], + [-0.5,170], + [-7,180], + [-14,186.5], + [-29,194], + [-45,197], + [-56.5,194.5], + [-65.5,191.5], + [-81.5,177.5], # 80th + [-87,169.5], + [-90,160], + [-92,152], + [-97.5,123.5], + [-104.5,113], + [-118,103], + [-132,98.5], + [-144,96.5], + [-155,95], + [-167,91], # 90th + [-180.5,79], + [-187,68.5], + [-191,53], + [-191,33], + [-191,13], + [-191,-3], + [-189,-20], + [-184.5,-34.5], + [-178.5,-50.5], + [-166.5,-66], # 100th + [-150,-80.5], + [-138.5,-87.5], + [-125,-93.5], + [-113.5,-96.5], + [-96,-98], + [-86,-98], + [-76,-98], + [-66,-98], + [-56,-98], + [-46,-98], + [-36,-98], + [-26,-98], + [-16,-98], # 113-th wp + ] + + def getWayPoints(self): + return self.pos_list \ No newline at end of file -- GitLab