diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..1d74e21965c4f858f5f818a270e64e1bfad7d843 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..4fa938b26fce46ac9eab7db76124dab3a7a925fa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +<<<<<<< HEAD +{} +======= +{ + "ros.distro": "galactic", + "python.autoComplete.extraPaths": [ + "/opt/ros/galactic/lib/python3.8/site-packages" + ] +} +>>>>>>> bdceb05... Add clamps srv diff --git a/actions_manager/CMakeLists.txt b/actions_manager/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..b185ebec185335f06d2d333e72e9d25b56de4ef7 --- /dev/null +++ b/actions_manager/CMakeLists.txt @@ -0,0 +1,199 @@ +cmake_minimum_required(VERSION 2.8.3) +project(actions_manager) + +## 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 + colors_msgs + ctrlpan_msgs + 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 +# colors_msgs# common_msgs# ctrlpan_msgs# 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 actions_manager +# CATKIN_DEPENDS colors_msgs common_msgs ctrlpan_msgs 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}/actions_manager.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/actions_manager_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 +# install(PROGRAMS +# scripts/my_python_script +# DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} +# ) + +## Mark executables and/or libraries for installation +# install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node +# ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} +# RUNTIME DESTINATION ${CATKIN_PACKAGE_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_actions_manager.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/actions_manager/nodes/actions_manager.py b/actions_manager/nodes/actions_manager.py new file mode 100755 index 0000000000000000000000000000000000000000..f1306abca2a3d099080555c04b6ca19f765f34af --- /dev/null +++ b/actions_manager/nodes/actions_manager.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +import rospy +from src.robot import Robot + +if __name__ == '__main__': + + rospy.init_node("actions_manager") + + try: + robot = Robot() + rospy.spin() + except rospy.ROSInterruptException: + pass diff --git a/actions_manager/package.xml b/actions_manager/package.xml new file mode 100644 index 0000000000000000000000000000000000000000..7d002bbb29a159a9f073dcca9da97104e12ce386 --- /dev/null +++ b/actions_manager/package.xml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<package format="2"> + <name>actions_manager</name> + <version>0.0.0</version> + <description>The actions_manager package</description> + + <!-- One maintainer tag required, multiple allowed, one person per tag --> + <!-- Example: --> + <!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> --> + <maintainer email="thomas@todo.todo">thomas</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/actions_manager</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>colors_msgs</build_depend> + <build_depend>ctrlpan_msgs</build_depend> + <build_depend>std_msgs</build_depend> + <build_export_depend>colors_msgs</build_export_depend> + <build_export_depend>ctrlpan_msgs</build_export_depend> + <build_export_depend>std_msgs</build_export_depend> + <exec_depend>colors_msgs</exec_depend> + <exec_depend>ctrlpan_msgs</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/actions_manager/src/action.py b/actions_manager/src/action.py new file mode 100644 index 0000000000000000000000000000000000000000..b471833404f54f75b65b2d14b705148b44dcf7b3 --- /dev/null +++ b/actions_manager/src/action.py @@ -0,0 +1,7 @@ +import queue + + +class Action(): + + def __init__(self): + self.task_queue = queue.Queue() diff --git a/actions_manager/src/position.py b/actions_manager/src/position.py new file mode 100644 index 0000000000000000000000000000000000000000..7d08753c731bace5b7830c716b0fd80e917f768a --- /dev/null +++ b/actions_manager/src/position.py @@ -0,0 +1,19 @@ +from math import sqrt + + +def Position(): + + def __init__(self, x, y, theta): + self.x = x + self.y = y + self.theta = theta + + def distance_sq(self, x, y): + dx = self.x - x + dy = self.y - y + return dx * dx + dy * dy + + def distance(self, x, y): + dx = self.x - x + dy = self.y - y + return sqrt(dx * dx + dy * dy) diff --git a/actions_manager/src/robot.py b/actions_manager/src/robot.py new file mode 100644 index 0000000000000000000000000000000000000000..49b28113ad45319864508731c5a456f93569efff --- /dev/null +++ b/actions_manager/src/robot.py @@ -0,0 +1,115 @@ +from time import sleep +from position import Position +from math import pi +from utils import compare_distance, compare_theta +import rospy +from geometry_msgs.msg import Twist +from std_srvs.srv import SetBool +from colors_msgs.msg import Colors + +LINEAR_SPEED = 0.2 +ROTATION_SPEED = 3.14 # rad.s-1 + +SLEEP_INTERVAL = 0.1 + + +class Robot(): + + def __init__(self): + self.position = Position(0, 0, 0) + self.left_color = None + self.right_color = None + + # TODO send twist to a multiplexer (velocity control node) for the navigation (containing LiDAR emergency stop) + self.cmd_vel_pub = rospy.Publisher( + "/actions_manager/cmd_vel", Twist, queue_size=5) + self.activate_windsocks_arm_srv = rospy.ServiceProxy( + "/ctrl_panel/arm", SetBool) + self.color_sensor_sub = rospy.Subscriber( + "/color_sensors", Colors, self.color_sensor_cb) + + def color_sensor_cb(self, data): + self.left_color = data.color_left + self.right_color = data.color_right + + def rotate_to(self, theta): + + theta = theta % (2 * pi) + + start_theta = self.position.theta + + dTheta = (theta - start_theta) % (2 * pi) + + # Optimize the rotation by taking the shortest angle position and inverting rotation direction if needed + clockwise = False + if dTheta >= pi: + theta = (2 * pi - theta) % (2 * pi) + clockwise = True + else: # 0 < theta < pi + clockwise = False + + while not compare_theta(theta, self.position.theta): + self.send_vel( + 0, -ROTATION_SPEED if clockwise else ROTATION_SPEED) + self.sleep(SLEEP_INTERVAL) + + def lower_windsocks_arm(self): + self.activate_windsocks_arm_srv(True) + + def raise_windsocks_arm(self): + self.activate_windsocks_arm_srv(False) + + def lower_multiclamp_arm(self): + pass + + def raise_multiclamp_arm(self): + pass + + def close_multiclamp_servo(self, servo_number): + pass + + def open_multiclamp_servo(self, servo_number): + pass + + def lower_monoclamp(self, left): + pass + + def raise_monoclamp(self, left): + pass + + def open_monoclamp(self, left): + pass + + def close_monoclamp(self, left): + pass + + def send_vel(self, lin_vel, ang_vel): + twist = Twist() + twist.linear.x = lin_vel + twist.linear.y = 0 + twist.linear.z = 0 + twist.angular.x = 0 + twist.angular.y = 0 + twist.angular.z = ang_vel + self.cmd_vel_pub.publish(twist) + + # /!\ /!\ /!\ /!\ /!\ + # TODO: slow down when getting close to the objective + + def forward(self, distance): + + start_position = self.position + + # We check that we approximately run the distance we wanted, if not, we continue to go forward + while not compare_distance(start_position, self.position, distance): + self.send_vel(LINEAR_SPEED, 0) + sleep(SLEEP_INTERVAL) + + def backward(self, distance): + + start_position = self.position + + # We check that we approximately run the distance we wanted, if not, we continue to go forward + while not compare_distance(start_position, self.position, distance): + self.send_vel(-LINEAR_SPEED, 0) + sleep(SLEEP_INTERVAL) diff --git a/actions_manager/src/tasks/follow_line.py b/actions_manager/src/tasks/follow_line.py new file mode 100644 index 0000000000000000000000000000000000000000..e7ba5c76ac197fd47f841fbf83ef70d81ccf0f16 --- /dev/null +++ b/actions_manager/src/tasks/follow_line.py @@ -0,0 +1,29 @@ +from time import sleep +from task import Task + + +class FollowLine(Task): + + def __init__(self, robot): + self.robot = robot + + def run(self): + + ang_vel = 0 + lin_vel = 0.1 + + update_interval = 0.03 + + # TODO add an end condition (eg. touching the wall) + while True: + if (self.robot.left_color == 1): + ang_vel = ang_vel + 0.001 + + if (self.robot.right_color == 1): + ang_vel = ang_vel - 0.001 + + if (self.robot.right_color == 2 and self.color.left_color == 2): + ang_vel = 0 + + self.robot.send_vel(lin_vel, ang_vel) + sleep(update_interval) diff --git a/actions_manager/src/tasks/goto.py b/actions_manager/src/tasks/goto.py new file mode 100644 index 0000000000000000000000000000000000000000..258727a0d8c2427fd1e19eaae27e0038401d4368 --- /dev/null +++ b/actions_manager/src/tasks/goto.py @@ -0,0 +1,28 @@ +from task import Task +from math import pi +from utils import compute_signed_angle, compare_distance, compare_theta + + +class Goto(Task): + + def __init__(self, robot, target_pos, backward): + self.robot = robot + self.target_pos = target_pos + self.backward = backward + + def run(self): + + if self.backward: + + angle_to_target = compute_signed_angle( + self.robot.position, self.target_pos) + angle_to_target = (angle_to_target + pi) % (2 * pi) + self.robot.rotate_to(angle_to_target) + self.robot.backward(self.robot.position.distance(self.target_pos)) + self.robot.rotate_to(self.target_pos.theta) + else: + angle_to_target = compute_signed_angle( + self.robot.position, self.target_pos) + self.robot.rotate_to(angle_to_target) + self.robot.forward(self.robot.position.distance(self.target_pos)) + self.robot.rotate_to(self.target_pos.theta) diff --git a/actions_manager/src/tasks/monoclamp_grab.py b/actions_manager/src/tasks/monoclamp_grab.py new file mode 100644 index 0000000000000000000000000000000000000000..b79a59c923708ee9878ad2a9f08671311dac96e6 --- /dev/null +++ b/actions_manager/src/tasks/monoclamp_grab.py @@ -0,0 +1,20 @@ +from time import sleep +from task import Task + + +class MonoclampGrab(Task): + + def __init__(self, robot, left): + self.robot = robot + self.left = left + + def run(self): + + self.robot.open_monoclamp(self.left) + sleep(0.5) + self.robot.lower_monoclamp(self.left) + sleep(1) + self.robot.close_monoclamp(self.left) + sleep(0.5) + self.robot.raise_monoclamp(self.left) + sleep(1) diff --git a/actions_manager/src/tasks/monoclamp_place.py b/actions_manager/src/tasks/monoclamp_place.py new file mode 100644 index 0000000000000000000000000000000000000000..2c18ce72d5b8b307e7c173ef904a4f5d10b6651d --- /dev/null +++ b/actions_manager/src/tasks/monoclamp_place.py @@ -0,0 +1,20 @@ +from time import sleep +from task import Task + + +class MonoclampPlace(Task): + + def __init__(self, robot, left): + self.robot = robot + self.left = left + + def run(self): + + self.robot.lower_monoclamp(self.left) + sleep(1) + self.robot.open_monoclamp(self.left) + sleep(0.5) + self.robot.raise_monoclamp(self.left) + sleep(1) + self.robot.close_monoclamp(self.left) + sleep(0.5) diff --git a/actions_manager/src/tasks/multiclamp_grab.py b/actions_manager/src/tasks/multiclamp_grab.py new file mode 100644 index 0000000000000000000000000000000000000000..ceba1e0309b4c55e31570a5e3901472c07320737 --- /dev/null +++ b/actions_manager/src/tasks/multiclamp_grab.py @@ -0,0 +1,24 @@ +from time import sleep +from task import Task + + +class MulticlampGrab(Task): + + def __init__(self, robot, cup_indexes={1, 2, 3, 4, 5}): + self.robot = robot + self.cup_indexes = cup_indexes + + def run(self): + + # todo open all + for cup_idx in self.cup_indexes: + self.robot.open_multiclamp_servo(cup_idx) + + self.robot.lower_multiclamp_arm() + sleep(1) # wait for the multiclamp to go down + + for cup_idx in self.cup_indexes: + self.robot.close_multiclamp_servo(cup_idx) + + self.robot.raise_multiclamp_arm() + sleep(1) # wait for the multiclamp to go up diff --git a/actions_manager/src/tasks/multiclamp_place.py b/actions_manager/src/tasks/multiclamp_place.py new file mode 100644 index 0000000000000000000000000000000000000000..cd7716da5457cee9db2ee3ef0914b9df279ab4ae --- /dev/null +++ b/actions_manager/src/tasks/multiclamp_place.py @@ -0,0 +1,23 @@ +from time import sleep +from task import Task + + +class MulticlampPlace(Task): + + def __init__(self, robot, cup_indexes={1, 2, 3, 4, 5}): + self.robot = robot + self.cup_indexes = cup_indexes + + def run(self): + + self.robot.lower_multiclamp_arm() + sleep(1) # wait for the multiclamp to go down + + for cup_idx in self.cup_indexes: + self.robot.open_multiclamp_servo(cup_idx) + + self.robot.raise_multiclamp_arm() + sleep(1) # wait for the multiclamp to go up + + for cup_idx in self.cup_indexes: + self.robot.close_multiclamp_servo(cup_idx) diff --git a/actions_manager/src/tasks/swing_windsocks.py b/actions_manager/src/tasks/swing_windsocks.py new file mode 100644 index 0000000000000000000000000000000000000000..a073ff26b0023cb1cc153dd20a20652905e6a008 --- /dev/null +++ b/actions_manager/src/tasks/swing_windsocks.py @@ -0,0 +1,30 @@ +from time import sleep +from task import Task +from utils import compare_distance, compare_theta +from math import pi + +TRAVEL_DISTANCE = 0.5 # meters + + +class SwingWindsocks(Task): + + def __init__(self, robot, state): + self.robot = robot + self.state = state + self.start_pos = None + + def run(self): + self.start_pos = self.robot.position + + # Lower the arm and wait for it to complete + # Ensure we are at the right rotation + # Go forward TRAVEL_DISTANCE meters + # Raise up the arm + # Wait for the arm to be completely up + + self.robot.rotate_to(-pi) + self.robot.lower_windsocks_arm() + sleep(0.5) + self.robot.forward(TRAVEL_DISTANCE) + self.robot.raise_windsocks_arm() + sleep(0.5) diff --git a/actions_manager/src/tasks/task.py b/actions_manager/src/tasks/task.py new file mode 100644 index 0000000000000000000000000000000000000000..2ce1ca40987e6b7dd731ab2f6ac517f4e339c391 --- /dev/null +++ b/actions_manager/src/tasks/task.py @@ -0,0 +1,23 @@ +from abc import abstractmethod + + +class Task(): + """ + A task is a small and atomic action to perform. + + For instance : goto a position, grab an object, ... + """ + + def estimatePointsAndTime(self): + """ + return a tuple (points, time) due to the task + This estimation is done for a specific state (robot position, ..) + """ + return (0, 0) + + @abstractmethod + def run(self): + """ + Blocking call to process the task + """ + pass diff --git a/actions_manager/src/utils.py b/actions_manager/src/utils.py new file mode 100644 index 0000000000000000000000000000000000000000..49411c52cc93ec49e6a457ac324272230beb2bc1 --- /dev/null +++ b/actions_manager/src/utils.py @@ -0,0 +1,32 @@ +from math import atan2, pi + +EPSILON_POSITION = 0.01 # +/- 1cm +EPSILON_ROTATION = 0.017 # +/- 1° + + +def compute_signed_angle(from_pos, to_pos): + """ + Return the angle of the vector going from from_pos to to_pos. The angle is included in the interval [0; 2*pi] + """ + vx = to_pos.x - from_pos.x + vy = to_pos.y - from_pos.y + signed_angle = atan2(vy, vx) + # Wrap between [0; 2pi] + signed_angle = signed_angle % (2 * pi) + return signed_angle + + +def compare_distance(pos_a, pos_b, distance=0): + """ + Return true if position A and position B are at a given distance + This internally uses EPSILON_POSITION to take imprecisions into account + """ + return pos_a.distance_sq(pos_b) - distance * distance <= EPSILON_POSITION * EPSILON_POSITION + + +def compare_theta(theta_a, theta_b): + """ + Return true if theta A and theta B are close enough. + This internally uses EPSILON_POSITION to take imprecisions into account + """ + return abs(theta_a - theta_b) <= EPSILON_ROTATION diff --git a/homologation/.gitignore b/coordinator/.gitignore similarity index 100% rename from homologation/.gitignore rename to coordinator/.gitignore diff --git a/homologation/CMakeLists.txt b/coordinator/CMakeLists.txt similarity index 99% rename from homologation/CMakeLists.txt rename to coordinator/CMakeLists.txt index d2fad16773968a2b53db4a44a680cb71815d9e31..59e0b2d5fb2de2fcc3e3bfcc6f058d86fb507fc9 100644 --- a/homologation/CMakeLists.txt +++ b/coordinator/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.0.2) -project(homologation) +project(coordinator) ## Compile as C++11, supported in ROS Kinetic and newer # add_compile_options(-std=c++11) diff --git a/homologation/config/front_laser.yaml b/coordinator/config/front_laser.yaml similarity index 100% rename from homologation/config/front_laser.yaml rename to coordinator/config/front_laser.yaml diff --git a/homologation/config/rear_laser.yaml b/coordinator/config/rear_laser.yaml similarity index 100% rename from homologation/config/rear_laser.yaml rename to coordinator/config/rear_laser.yaml diff --git a/coordinator/launch/homologation.launch b/coordinator/launch/homologation.launch new file mode 100644 index 0000000000000000000000000000000000000000..0b9b06655c6972a8de5ea75cc8eff8e112bc7e12 --- /dev/null +++ b/coordinator/launch/homologation.launch @@ -0,0 +1,44 @@ +<launch> + + <machine name="lagalere" address="lagalere.local" env-loader="/home/lagalere/ros_ws/env_loader.sh" default="false" user="lagalere" /> + <machine name="local" address="localhost" default="true" /> + + <arg name="mccd_port" default="/dev/mccd"/> + <arg name="mccd_baud" default="115200"/> + + <arg name="ctrl_panel_port" default="/dev/ctrl_panel"/> + <arg name="ctrl_panel_baud" default="57600"/> + + <node machine="lagalere" name="mccd" pkg="rosserial_python" type="serial_node.py" output="screen"> + <param name="port" type="string" value="$(arg mccd_port)"/> + <param name="baud" type="int" value="$(arg mccd_baud)"/> + <param name="sampling_period" type="int" value="50"/> + <param name="speed_profile/linear_speed_acceleration_max" type="double" value="0.8"/> + <param name="speed_profile/linear_speed_deceleration_max" type="double" value="0.8"/> + <param name="speed_profile/angular_speed_acceleration_max" type="double" value="3.14"/> + <param name="speed_profile/angular_speed_deceleration_max" type="double" value="3.14"/> + <rosparam param="left_motor_gains">[3.285, 64.40766, 0.0]</rosparam> + <rosparam param="right_motor_gains">[3.240, 66.48427, 0.0]</rosparam> + </node> + + <node machine="lagalere" name="ctrl_panel" pkg="rosserial_python" type="serial_node.py" output="screen"> + <param name="port" value="$(arg ctrl_panel_port)" /> + <param name="baud" value="$(arg ctrl_panel_baud)" /> + </node> + + <!-- <node machine="lagalere" name="clamp" pkg="rosserial_python" type="serial_node.py" output="screen"> + <param name="port" value="/dev/clamp" /> + <param name="baud" value="57600" /> + </node> --> + + <node machine="lagalere" name="rplidarNode" pkg="rplidar_ros" type="rplidarNode" respawn="true" output="screen"> + <param name="serial_port" type="string" value="/dev/ttyUSB0"/> + <param name="serial_baudrate" type="int" value="115200"/> + <param name="frame_id" type="string" value="laser"/> + <param name="inverted" type="bool" value="false"/> + <param name="angle_compensate" type="bool" value="true"/> + </node> + + <node machine="local" name="homologation" pkg="coordinator" type="homologation.py" output="screen"/> + +</launch> \ No newline at end of file diff --git a/coordinator/launch/match.launch b/coordinator/launch/match.launch new file mode 100644 index 0000000000000000000000000000000000000000..bff7bc3555805c2e9ffa8e72f5bc1adaf202f713 --- /dev/null +++ b/coordinator/launch/match.launch @@ -0,0 +1,98 @@ +<launch> + + <machine name="lagalere" address="lagalere.local" env-loader="/home/lagalere/ros_ws/env_loader.sh" default="false" user="lagalere" /> + <machine name="local" address="localhost" default="true" /> + + <arg name="mccd_port" default="/dev/mccd"/> + <arg name="mccd_baud" default="115200"/> + + <arg name="ctrl_panel_port" default="/dev/ctrl_panel"/> + <arg name="ctrl_panel_baud" default="57600"/> + + <arg name="enable_raw" default="false"/> + <arg name="enable_imv" default="false"/> + <arg name="camera_id" default="0"/> + <arg name="camera_frame_id" default="raspicam"/> + <arg name="camera_name" default="picam"/> + + <!-- namespace for camera input --> + <arg name="camera" default="/raspicam_node"/> + <arg name="image" default="image"/> + <arg name="transport" default="compressed"/> + <arg name="fiducial_len" default="0.14"/> + <arg name="dictionary" default="0"/> + <arg name="do_pose_estimation" default="true"/> + <!-- If vis_msgs set to true, pose estimation will be published with ROS standard vision_msgs --> + <arg name="vis_msgs" default="false"/> + <arg name="ignore_fiducials" default="" /> + <arg name="fiducial_len_override" default="" /> + <arg name="verbose" default="false"/> + + <node machine="lagalere" name="mccd" pkg="rosserial_python" type="serial_node.py"> + <param name="port" type="string" value="$(arg mccd_port)"/> + <param name="baud" type="int" value="$(arg mccd_baud)"/> + <param name="sampling_period" type="int" value="200"/> + <param name="speed_profile/linear_speed_acceleration_max" type="double" value="0.8"/> + <param name="speed_profile/linear_speed_deceleration_max" type="double" value="0.8"/> + <param name="speed_profile/angular_speed_acceleration_max" type="double" value="3.14"/> + <param name="speed_profile/angular_speed_deceleration_max" type="double" value="3.14"/> + <rosparam param="left_motor_gains">[3.285, 64.40766, 0.0]</rosparam> + <rosparam param="right_motor_gains">[3.240, 66.48427, 0.0]</rosparam> + </node> + + <node machine="lagalere" name="ctrl_panel" pkg="rosserial_python" type="serial_node.py"> + <param name="port" value="$(arg ctrl_panel_port)" /> + <param name="baud" value="$(arg ctrl_panel_baud)" /> + </node> + + <node machine="lagalere" name="clamp" pkg="rosserial_python" type="serial_node.py" output="screen"> + <param name="port" value="/dev/clamp" /> + <param name="baud" value="57600" /> + </node> + + <node machine="lagalere" name="rplidarNode" pkg="rplidar_ros" type="rplidarNode" respawn="true"> + <param name="serial_port" type="string" value="/dev/ttyUSB0"/> + <param name="serial_baudrate" type="int" value="115200"/> + <param name="frame_id" type="string" value="laser"/> + <param name="inverted" type="bool" value="false"/> + <param name="angle_compensate" type="bool" value="true"/> + </node> + + <node machine="lagalere" type="raspicam_node" pkg="raspicam_node" name="raspicam_node" output="screen"> + <param name="private_topics" value="true"/> + + <param name="camera_frame_id" value="$(arg camera_frame_id)"/> + <param name="enable_raw" value="$(arg enable_raw)"/> + <param name="enable_imv" value="$(arg enable_imv)"/> + <param name="camera_id" value="$(arg camera_id)"/> + + <param name="camera_info_url" value="package://raspicam_node/camera_info/camerav2_1280x960.yaml"/> + <param name="camera_name" value="$(arg camera_name)"/> + <param name="width" value="1280"/> + <param name="height" value="960"/> + + <param name="framerate" value="10"/> + <param name="exposure_mode" value="antishake"/> + <param name="shutter_speed" value="0"/> + </node> + + + <node machine="lagalere" pkg="aruco_detect" name="aruco_detect" + type="aruco_detect" output="screen" respawn="false"> + <param name="image_transport" value="$(arg transport)"/> + <param name="publish_images" value="false" /> + <param name="fiducial_len" value="$(arg fiducial_len)"/> + <param name="dictionary" value="$(arg dictionary)"/> + <param name="do_pose_estimation" value="$(arg do_pose_estimation)"/> + <param name="vis_msgs" value="$(arg vis_msgs)"/> + <param name="ignore_fiducials" value="$(arg ignore_fiducials)"/> + <param name="fiducial_len_override" value="$(arg fiducial_len_override)"/> + <param name="verbose" value="$(arg verbose)"/> + <remap from="camera/compressed" + to="$(arg camera)/$(arg image)/$(arg transport)"/> + <remap from="camera_info" to="$(arg camera)/camera_info"/> + </node> + + <node machine="lagalere" name="match" pkg="coordinator" type="match.py" output="screen"/> + +</launch> diff --git a/coordinator/nodes/homologation.py b/coordinator/nodes/homologation.py new file mode 100755 index 0000000000000000000000000000000000000000..0fa076c9fb6050c279f7b2e9846fe5dd998b02e6 --- /dev/null +++ b/coordinator/nodes/homologation.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python3 + +import threading +import time +import rospy +from geometry_msgs.msg import Twist +from mccd_msgs.msg import Odometry +from sensor_msgs.msg import LaserScan as Scan +from std_srvs.srv import SetBool +from std_msgs.msg import Bool +from math import radians, degrees, pi, sqrt + + +emergency_distance = 0.40 # in meters +emergency_min_count = 2 + + +class CoordinatorNode(): + + def __init__(self): + self.change_pwm_service = rospy.ServiceProxy( + '/mccd/enable_pwm', SetBool) + self.cmd_vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10) + self.pull_cord_status_sub = rospy.Subscriber( + '/pull_cord_status', Bool, self.pull_cord_status_callback) + self.pull_cord_status = False + self.odom_sub = rospy.Subscriber( + "/mccd/odom", Odometry, self.odom_callback) + + self.flag_state_pub = rospy.Publisher( + "/flag_state", Bool, queue_size=1) + + self.linear_speed = 0 + self.theta = 0 + self.x = 0 + self.y = 0 + + self.lidar_subscriber = rospy.Subscriber( + 'scan', Scan, self.scan_callback) + + self.scan = None + + print("Coordinator initialized.") + + def odom_callback(self, odom): + self.linear_speed = odom.linear_speed + self.theta = odom.theta + self.x = odom.x + self.y = odom.y + + def change_pwm(self, enable): + print("Waiting for service '/mccd/enable_pwm'") + rospy.wait_for_service('/mccd/enable_pwm') + + try: + res = self.change_pwm_service(enable) + return res + except rospy.ServiceException as e: + print("Service call failed: %s" % e) + + def scan_callback(self, scan): + self.scan = scan + + def set_speed(self, linear_speed, angular_speed): + + linear_speed_cmd = linear_speed + angular_speed_cmd = angular_speed + + # First check if we can go in the wanted direction (ie. not obstacle in front/back if going forward/backward) + if self.scan is None: + linear_speed_cmd = 0 + angular_speed_cmd = 0 + else: + counter = 0 + + for i, distance in enumerate(self.scan.ranges): + angle = self.scan.angle_min + i * self.scan.angle_increment + + if distance < emergency_distance: + counter = counter + 1 + # Stop if we detected something in the front and we are going forward + # if linear_speed > 0 and (radians(-180) < angle < radians(-60-90) or radians(60+90) < angle < radians(180)): + # counter = counter + 1 + # # Stop if we detected something in the back and we are going backward + # elif linear_speed < 0 and radians(-60) < angle < radians(60): + # counter = counter + 1 + + if counter >= emergency_min_count: + # rospy.loginfo("Emergency stop") + linear_speed_cmd = 0 + angular_speed_cmd = 0 + + twist = Twist() + twist.linear.x = linear_speed_cmd + twist.angular.z = angular_speed_cmd + self.cmd_vel_pub.publish(twist) + + def pull_cord_status_callback(self, data): + self.pull_cord_status = data.data + + def move(self, target_distance, forward): + + start_x = self.x + start_y = self.y + epsilon = 0.01 + + distance = 0 + + while abs(target_distance - distance) >= epsilon: + # rospy.loginfo("Distance: {}".format(distance)) + self.set_speed(0.1 if forward else -0.1, 0) + rospy.sleep(0.02) + + distance = sqrt((self.x - start_x) * (self.x - start_x) + + (self.y - start_y) * (self.y - start_y)) + + def run(self): + + self.change_pwm(True) + + while self.pull_cord_status != True: + self.set_speed(0, 0) + time.sleep(0.02) + + self.change_pwm(True) + + start = rospy.get_rostime().secs + + rospy.loginfo("Starting !") + + rospy.loginfo("Going forward") + self.move(1, forward=True) + rospy.loginfo("Finished forward") + + rospy.loginfo("Going backward") + self.move(0.5, forward=False) + rospy.loginfo("Finished backward") + + while True: + if rospy.get_rostime().secs - start >= 96: + flag_state = Bool() + flag_state.data = True + self.flag_state_pub.publish(flag_state) + self.set_speed(0, 0) + time.sleep(0.02) + + self.set_speed(0, 0) + self.change_pwm(False) + + +if __name__ == '__main__': + try: + rospy.init_node("coordinator") + coordinator = CoordinatorNode() + coordinator.run() + rospy.spin() + except rospy.ROSInterruptException: + pass diff --git a/coordinator/nodes/line_follower.py b/coordinator/nodes/line_follower.py new file mode 100755 index 0000000000000000000000000000000000000000..ef46a9c8da5e7bb82180ff644d9d359396794e06 --- /dev/null +++ b/coordinator/nodes/line_follower.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +import threading +import time +import rospy +from geometry_msgs.msg import Twist, Vector3 +from std_srvs.srv import SetBool + + +class HSVColor(): + def __init__(self, h, s, v): + self.h = h + self.s = s + self.v = v + + +class CoordinatorNode(): + + def __init__(self): + self.change_pwm_service = rospy.ServiceProxy( + '/mccd/enable_pwm', SetBool) + self.cmd_vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1) + self.left_color_sub = rospy.Subscriber( + 'color_sensor/left', Vector3, self.left_color_cb) + self.right_color_sub = rospy.Subscriber( + 'color_sensor/right', Vector3, self.right_color_cb) + self.exec_thread = threading.Thread(target=self.run) + self.exec_thread.start() + + print("Coordinator initialized.") + + def change_pwm(self, enable): + print("Waiting for service '/mccd/enable_pwm'") + rospy.wait_for_service('/mccd/enable_pwm') + + try: + res = self.change_pwm_service(enable) + return res + except rospy.ServiceException as e: + print("Service call failed: %s" % e) + + def send_vel(self, linear_vel, angular_vel): + twist = Twist() + twist.linear.x = linear_vel + twist.linear.y = 0 + twist.linear.z = 0 + twist.angular.x = 0 + twist.angular.y = 0 + twist.angular.z = angular_vel + self.cmd_vel_pub.publish(twist) + + def left_color_cb(self, hsv): + self.last_left_color = self.left_color + self.left_color = HSVColor(hsv.x, hsv.y, hsv.z) + + def right_color_cb(self, hsv): + self.last_right_color = self.right_color + self.right_color = HSVColor(hsv.x, hsv.y, hsv.z) + + def line_follow_update(self): + # TODO Line follow logic + + # Left sees white + if self.left_color.s > 0.96: + rospy.loginfo("left sees white") + + self.send_vel(0.1, 0) + time.sleep(0.1) + + def run(self): + + rospy.loginfo("Waiting for 2s before starting...") + time.sleep(2.0) + + self.change_pwm(True) + self.send_vel(0, 0) + + start_time = rospy.get_rostime() + + # Roule pendant 10 secondes + while rospy.get_rostime().secs - start_time.secs <= 10: + self.line_follow_update() + + self.send_vel(0, 0) + time.sleep(0.2) + self.change_pwm(False) + + +if __name__ == '__main__': + try: + rospy.init_node("line_follower") + CoordinatorNode() + rospy.spin() + except rospy.ROSInterruptException: + pass diff --git a/coordinator/nodes/match.py b/coordinator/nodes/match.py new file mode 100755 index 0000000000000000000000000000000000000000..8dc26edce7a6073b9374858d8c0dd28c45cdc982 --- /dev/null +++ b/coordinator/nodes/match.py @@ -0,0 +1,598 @@ +#!/usr/bin/env python3 + +import sys +import signal +import threading +import time +import rospy +from mccd_msgs.msg import CmdVel +from mccd_msgs.msg import Odometry +from colors_msgs.msg import Colors +from std_msgs.msg import Int8, Empty +from sensor_msgs.msg import LaserScan as Scan +from std_srvs.srv import SetBool +from std_msgs.msg import Bool, Int32 +from fiducial_msgs.msg import FiducialTransformArray +from math import radians, degrees, pi, sqrt, atan2, sin, cos, asin + + +emergency_distance = 0.40 # in meters +emergency_min_count = 2 + +lidar_checking_range = 90 + +send_interval = 0.1 + +angle_correction_factor = 0.92345679 +distance_correction_offset = 0.028 + +start_slope_distance_threshold = 0.03 +end_slope_distance_threshold = 0.20 +max_lin_speed = 0.35 +min_lin_speed = 0.05 + + +class CoordinatorNode(): + + def __init__(self): + self.aruco_sub = rospy.Subscriber( + "/fiducial_transforms", FiducialTransformArray, self.aruco_cb) + self.change_pwm_service = rospy.ServiceProxy( + '/mccd/enable_pwm', SetBool) + self.cmd_vel_pub = rospy.Publisher( + '/mccd/cmd_vel', CmdVel, queue_size=5) + self.pull_cord_status_sub = rospy.Subscriber( + '/pull_cord_status', Bool, self.pull_cord_status_callback) + self.pull_cord_status = False + self.odom_sub = rospy.Subscriber( + "/mccd/odom", Odometry, self.odom_callback) + self.flag_state_pub = rospy.Publisher( + "/flag_state", Bool, queue_size=5) + self.arm_state_pub = rospy.Publisher( + "/arm_state", Bool, queue_size=5) + self.estimated_score_pub = rospy.Publisher( + "/estimated_score", Int32, queue_size=1) + self.colors_sub = rospy.Subscriber( + "/color_sensors", Colors, self.colors_cb) + self.fdc_sub = rospy.Subscriber("/limit_switch", Int8, self.fdc_cb) + self.team_sub = rospy.Subscriber("/team", Bool, self.team_cb) + + self.reset_error_pub = rospy.Publisher( + "/mccd/reset_error", Empty, queue_size=1) + + self.clamps_pub = rospy.Publisher( + '/clamp/cmd_backclamp', Int8, queue_size=1) + + self.linear_speed = 0 + self.theta = 0 + self.x = 0 + self.y = 0 + + self.disable_lidar = False + + self.left_fdc_pressed = 0 + self.right_fdc_pressed = 0 + + self.color_left = None + self.color_right = None + + self.lidar_subscriber = rospy.Subscriber( + 'scan', Scan, self.scan_callback) + + self.scan = None + self.team = None + + self.weathercock_info = None + self.enable_aruco_check = True + + print("Coordinator initialized.") + + def euler_from_quaternion(self, x, y, z, w): + """ + Convert a quaternion into euler angles (roll, pitch, yaw) + roll is rotation around x in radians (counterclockwise) + pitch is rotation around y in radians (counterclockwise) + yaw is rotation around z in radians (counterclockwise) + """ + t0 = +2.0 * (w * x + y * z) + t1 = +1.0 - 2.0 * (x * x + y * y) + roll_x = atan2(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_y = asin(t2) + + t3 = +2.0 * (w * z + x * y) + t4 = +1.0 - 2.0 * (y * y + z * z) + yaw_z = atan2(t3, t4) + + return roll_x, pitch_y, yaw_z # in radians + + def aruco_cb(self, data): + # if self.enable_aruco_check: + # return + + if (data.transforms is None): + return + for aruco in data.transforms: + if (aruco.fiducial_id == 17): + rot = aruco.transform.rotation + (x, y, z) = self.euler_from_quaternion( + rot.x, rot.y, rot.z, rot.w) + + if (y > 0): + self.weathercock_info = "Nord" + else: + self.weathercock_info = "Sud" + print(self.weathercock_info) + + def colors_cb(self, data): + self.color_left = data.color_left + self.color_right = data.color_right + + def team_cb(self, msg): + self.team = msg.data + + def fdc_cb(self, msg): + self.left_fdc_pressed = msg.data & 0b1 + self.right_fdc_pressed = (msg.data >> 0b1) & 0b1 + pass + + def odom_callback(self, odom): + self.linear_speed = odom.linear_speed + self.theta = odom.theta + self.x = odom.x + self.y = odom.y + + def change_pwm(self, enable): + print("Waiting for service '/mccd/enable_pwm'") + rospy.wait_for_service('/mccd/enable_pwm') + + try: + res = self.change_pwm_service(enable) + return res + except rospy.ServiceException as e: + print("Service call failed: %s" % e) + + def reset_error(self): + msg = Empty() + self.reset_error_pub.publish(msg) + + def scan_callback(self, scan): + self.scan = scan + + def set_speed(self, linear_speed, angular_speed): + + linear_speed_cmd = linear_speed + angular_speed_cmd = angular_speed + + # First check if we can go in the wanted direction (ie. not obstacle in front/back if going forward/backward) + if self.scan is None: + linear_speed_cmd = 0 + angular_speed_cmd = 0 + else: + counter = 0 + + for i, distance in enumerate(self.scan.ranges): + angle = self.scan.angle_min + i * self.scan.angle_increment + + if distance < emergency_distance: + # Stop if we detected something in the front and we are going forward + if linear_speed > 0 and (radians(-180) < angle < radians(-lidar_checking_range // 2 - 90) or radians(lidar_checking_range // 2 + 90) < angle < radians(180)): + counter = counter + 1 + # Stop if we detected something in the back and we are going backward + elif linear_speed < 0 and radians(-lidar_checking_range // 2) < angle < radians(lidar_checking_range // 2): + counter = counter + 1 + + if counter >= emergency_min_count and not self.disable_lidar: + linear_speed_cmd = 0 + angular_speed_cmd = 0 + + cmd_vel_msg = CmdVel() + cmd_vel_msg.linear_velocity = linear_speed_cmd + cmd_vel_msg.angular_velocity = angular_speed_cmd + self.cmd_vel_pub.publish(cmd_vel_msg) + + def pull_cord_status_callback(self, data): + self.pull_cord_status = data.data + + def move(self, target_distance, forward): + + target_distance = target_distance - distance_correction_offset + distance_accum = 0 + last_x = self.x + last_y = self.y + + while distance_accum < target_distance: + + start_slope_speed = min_lin_speed + distance_accum / \ + start_slope_distance_threshold * \ + (max_lin_speed - min_lin_speed) + end_slope_speed = min_lin_speed + \ + (target_distance - distance_accum) / \ + end_slope_distance_threshold * (max_lin_speed - min_lin_speed) + + speed = max(min_lin_speed, min(max_lin_speed, + min(start_slope_speed, end_slope_speed))) + + self.set_speed(speed if forward else -speed, 0) + + distance_accum = distance_accum + sqrt((self.x - last_x) * (self.x - last_x) + + (self.y - last_y) * (self.y - last_y)) + + last_x = self.x + last_y = self.y + time.sleep(send_interval) + + def shortest_angle(self, from_angle, to_angle): + return atan2(sin(to_angle - from_angle), cos(to_angle - from_angle)) + + def turn(self, theta, speed=0.5): + theta = theta * angle_correction_factor + + last_theta = self.theta + theta_accum = 0 # always positive + + while theta_accum < abs(theta): + + dTheta = self.shortest_angle(last_theta, self.theta) + theta_accum = theta_accum + abs(dTheta) + + if abs(theta_accum - abs(theta)) <= radians(10): + self.set_speed(0, 0.25 if theta > 0 else -0.25) + else: + self.set_speed(0, speed if theta > 0 else -speed) + + last_theta = self.theta + time.sleep(send_interval) + + def brake_for_n_seconds(self, secs): + + start = rospy.get_rostime().secs + + while rospy.get_rostime().secs - start < secs: + self.set_speed(0, 0) + time.sleep(send_interval) + + def open_clamp(self, left): + + clamp_msg = Int8() + + if left: + clamp_msg.data = 21 + else: + clamp_msg.data = 11 + + self.clamps_pub.publish(clamp_msg) + time.sleep(0.01) + self.clamps_pub.publish(clamp_msg) + + def close_clamp(self, left): + clamp_msg = Int8() + if left: + clamp_msg.data = 20 + else: + clamp_msg.data = 10 + + self.clamps_pub.publish(clamp_msg) + time.sleep(0.01) + self.clamps_pub.publish(clamp_msg) + + def raise_clamps(self): + clamp_msg = Int8() + clamp_msg.data = 1 + self.clamps_pub.publish(clamp_msg) + time.sleep(0.01) + self.clamps_pub.publish(clamp_msg) + + def store_clamps(self): + clamp_msg = Int8() + clamp_msg.data = 0 + self.clamps_pub.publish(clamp_msg) + time.sleep(0.01) + self.clamps_pub.publish(clamp_msg) + + def lower_flag(self): + msg = Bool() + msg.data = False + self.flag_state_pub.publish(msg) + time.sleep(0.1) + self.flag_state_pub.publish(msg) + + def raise_flag(self): + msg = Bool() + msg.data = True + self.flag_state_pub.publish(msg) + time.sleep(0.01) + self.flag_state_pub.publish(msg) + + def lower_arm(self): + msg = Bool() + msg.data = True + self.arm_state_pub.publish(msg) + time.sleep(0.01) + self.arm_state_pub.publish(msg) + + def raise_arm(self): + msg = Bool() + msg.data = False + self.arm_state_pub.publish(msg) + time.sleep(0.01) + self.arm_state_pub.publish(msg) + + def hit_wall(self, forward=True, timeout=5): + start_fdc = rospy.get_rostime().secs + + while not (self.left_fdc_pressed and self.right_fdc_pressed): + + self.set_speed(0.2 if forward else -0.2, 0) + time.sleep(send_interval) + + if rospy.get_rostime().secs - start_fdc > timeout: + return False + + self.reset_error() + self.brake_for_n_seconds(0.2) + return True + + def run(self): + + self.raise_arm() + self.lower_flag() + self.store_clamps() + + self.reset_error() + self.set_speed(0, 0) + self.change_pwm(True) + + rospy.loginfo("Ready") + + while self.pull_cord_status != True: + self.set_speed(0, 0) + time.sleep(send_interval) + + rospy.loginfo("Tirette retirée") + + start = rospy.get_rostime().secs + + hit_ok = True + + if self.team: # yellow + + self.move(0.5, forward=True) + self.turn(radians(-90)) + self.move(0.25, forward=True) + self.turn(radians(88)) + self.move(0.35, forward=False) + + rospy.loginfo("Ecocup poussées") + self.move(0.15, forward=True) + self.turn(radians(-90)) + self.hit_wall() + rospy.loginfo("Premier mur touché") + self.move(0.07, forward=False) + self.turn(radians(-90)) + hit_ok = self.hit_wall() + + if hit_ok: + rospy.loginfo("Second mur touché") + + self.move(0.06, forward=False) + + rospy.loginfo("Activation du phare") + self.set_speed(0, 0) + time.sleep(send_interval) + self.lower_arm() + self.brake_for_n_seconds(1) + self.raise_arm() + self.brake_for_n_seconds(1) + rospy.loginfo("Phare activé") + + self.disable_lidar = True + + # Recule + self.move(0.55, forward=False) + self.move(0.10, forward=True) + self.turn(radians(-90)) + + self.disable_lidar = False + + # Retourne vers le port côté gauche + self.move(0.82, forward=True) + self.turn(radians(-90)) + # Pousse l'autre écocup + self.move(0.2, forward=False) + + # Part du chenal gauche et va se calibrer contre le premier mur + self.move(0.15, forward=True) + self.turn(radians(90)) + self.hit_wall(timeout=999) + + # Calibration sur le second mur + self.move(0.06, forward=False) + self.turn(radians(92)) + self.hit_wall() + + # Tourne, puis baisse le bras, puis avance pour faire basculer les manches à air + self.move(0.06, forward=False) + self.turn(radians(189)) + self.lower_arm() + self.brake_for_n_seconds(2) + self.move(0.41, forward=True) + # Tourne pour faire basculer la deuxième + self.turn(radians(-90)) + self.raise_arm() + self.brake_for_n_seconds(1) + + # Se retourne pour se recalibrer sur le mur + self.move(0.05, forward=True) + self.turn(radians(-180), 1.0) + self.hit_wall() + + # Repart et va lire la valeur de la girouette + self.move(0.6, forward=False) + self.turn(radians(30)) + + start_girouette = rospy.get_time() + count_north = 0 + count_south = 0 + + while rospy.get_time() - start_girouette < 2: + if self.weathercock_info == "Nord": + count_north = count_north + 1 + elif self.weathercock_info == "Sud": + count_south = count_south + 1 + self.set_speed(0, 0) + time.sleep(send_interval) + + # Prend la décision de la zone de mouillage + self.turn(radians(-30)) + + if count_north > count_south: + self.move(0.60, forward=False) + + self.turn(radians(-90)) + self.move(0.2, forward=False) + + else: + self.set_speed(0, 0) + self.reset_error() + self.change_pwm(False) + else: # blue + self.move(0.5, forward=True) + self.turn(radians(90)) + self.move(0.28, forward=True) + self.turn(radians(-88)) + self.move(0.35, forward=False) + + rospy.loginfo("Ecocup poussées") + self.move(0.15, forward=True) + self.turn(radians(90)) + self.hit_wall() + rospy.loginfo("Premier mur touché") + self.move(0.07, forward=False) + self.turn(radians(90)) + hit_ok = self.hit_wall() + + if hit_ok: + rospy.loginfo("Second mur touché") + + self.move(0.06, forward=False) + + self.turn(radians(-190)) + + rospy.loginfo("Activation du phare") + self.set_speed(0, 0) + time.sleep(send_interval) + self.lower_arm() + self.brake_for_n_seconds(2) + self.raise_arm() + self.brake_for_n_seconds(1) + rospy.loginfo("Phare activé") + + self.disable_lidar = True + + # Recule + self.move(0.55, forward=True) + self.move(0.10, forward=False) + self.turn(radians(-90)) + + self.disable_lidar = False + + # Retourne vers le port côté gauche + self.move(0.82, forward=True) + self.turn(radians(-90)) + # Pousse l'autre écocup + self.move(0.38, forward=True) + + # Part du chenal gauche et va se calibrer contre le premier mur + self.move(0.15, forward=False) + self.turn(radians(90)) + self.hit_wall(timeout=999) + + # Calibration sur le second mur + self.move(0.06, forward=False) + self.turn(radians(-92)) + self.hit_wall() + + # Tourne, puis baisse le bras, puis avance pour faire basculer les manches à air + self.move(0.06, forward=False) + + self.turn(radians(-90)) + self.lower_arm() + self.brake_for_n_seconds(1) + self.turn(radians(90)) + + self.move(0.41, forward=False) + + self.raise_arm() + self.brake_for_n_seconds(1) + self.turn(radians(90)) + + # Se recalibre sur le mur + self.hit_wall() + + # Repart et va lire la valeur de la girouette + self.move(0.6, forward=False) + self.turn(radians(-30)) + + start_girouette = rospy.get_time() + count_north = 0 + count_south = 0 + + while rospy.get_time() - start_girouette < 2: + if self.weathercock_info == "Nord": + count_north = count_north + 1 + elif self.weathercock_info == "Sud": + count_south = count_south + 1 + self.set_speed(0, 0) + time.sleep(send_interval) + + # Prend la décision de la zone de mouillage + self.turn(radians(30)) + + if count_north > count_south: + self.move(0.60, forward=False) + + self.turn(radians(90)) + self.move(0.2, forward=False) + + else: + self.set_speed(0, 0) + self.reset_error() + self.change_pwm(False) + + score_msg = Int32() + score_msg.data = 66 + + flag_state = Bool() + flag_state.data = True + + while True: + + if rospy.get_rostime().secs - start > 95: + self.flag_state_pub.publish(flag_state) + self.estimated_score_pub.publish(score_msg) + self.set_speed(0, 0) + time.sleep(send_interval) + + self.change_pwm(False) + + +def signal_handler(sig, frame): + print('Goodbye!') + sys.exit(0) + + +if __name__ == '__main__': + + signal.signal(signal.SIGINT, signal_handler) + + try: + rospy.init_node("match") + + coordinator = CoordinatorNode() + coordinator.run() + except rospy.ROSInterruptException: + pass diff --git a/homologation/package.xml b/coordinator/package.xml similarity index 94% rename from homologation/package.xml rename to coordinator/package.xml index 3920eaa3baacc0f800229375633f1fb0c0b0a773..b1951db9f2805c1d627e73c39c6b602f17935176 100644 --- a/homologation/package.xml +++ b/coordinator/package.xml @@ -1,8 +1,8 @@ <?xml version="1.0"?> <package format="2"> - <name>homologation</name> + <name>coordinator</name> <version>0.1.0</version> - <description>Homologation stack package</description> + <description>Coordinator stack package</description> <maintainer email="charles.javerliat@gmail.com">Charles JAVERLIAT</maintainer> <license>GPLv3</license> diff --git a/homologation/scripts/homologation.py b/homologation/scripts/homologation.py deleted file mode 100644 index 78840b04ec99ee3c4688a407c8d2e186e302db53..0000000000000000000000000000000000000000 --- a/homologation/scripts/homologation.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python - -import threading -import time -import rospy -from geometry_msgs.msg import Twist -from sensor_msgs.msg import LaserScan as Scan -from std_srvs.srv import SetBool, SetBoolResponse -from std_msgs.msg import Bool - - -emergency_distance = 0.45 # in meters -emergency_min_count = 2 - - -class CoordinatorNode(): - - def __init__(self): - self.change_pwm_service = rospy.ServiceProxy( - '/mccd/enable_pwm', SetBool) - self.cmd_vel_pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1) - self.pull_cord_status_sub = rospy.Subscriber( - '/pull_cord_status', Bool, self.pull_cord_status_callback) - self.pull_cord_status = False - - self.emergency_count = 0 - - # TODO(cjaverliat): subscribe to front_scan and rear_scan instead and check on this - self.lidar_subscriber = rospy.Subscriber( - 'scan', Scan, self.scan_callback) - - self.exec_thread = threading.Thread(target=self.run) - self.exec_thread.start() - - print("Coordinator initialized.") - - def change_pwm(self, enable): - print("Waiting for service '/mccd/enable_pwm'") - rospy.wait_for_service('/mccd/enable_pwm') - - try: - res = self.change_pwm_service(enable) - return res - except rospy.ServiceException as e: - print("Service call failed: %s" % e) - - def scan_callback(self, scan): - if (min(scan.ranges) <= emergency_distance): - self.emergency_count = self.emergency_count + 1 - if (self.emergency_count >= emergency_min_count): - self.emergency_stop = True - else: - self.emergency_count = 0 - self.emergency_stop = False - - def pull_cord_status_callback(self, data): - self.pull_cord_status = data.data - - def send_vel(self, linear_vel, angular_vel): - twist = Twist() - twist.linear.x = linear_vel - twist.linear.y = 0 - twist.linear.z = 0 - twist.angular.x = 0 - twist.angular.y = 0 - twist.angular.z = angular_vel - self.cmd_vel_pub.publish(twist) - - def run(self): - - self.change_pwm(True) - - while self.pull_cord_status != True: - self.send_vel(0, 0) - time.sleep(0.3) - - start_time = rospy.get_rostime() - - # Roule pendant 3 secondes - while rospy.get_rostime().secs - start_time.secs <= 3: - - if self.emergency_stop: - self.send_vel(0, 0) - else: - self.send_vel(0.1, 0) - - time.sleep(0.1) - - self.send_vel(0, 0) - time.sleep(0.2) - self.change_pwm(False) - - -if __name__ == '__main__': - try: - rospy.init_node("coordinator") - CoordinatorNode() - rospy.spin() - except rospy.ROSInterruptException: - pass diff --git a/velocity_controller/CMakeLists.txt b/velocity_controller/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..0bf44cbbf372f5205912f096141c73d2c76be48c --- /dev/null +++ b/velocity_controller/CMakeLists.txt @@ -0,0 +1,206 @@ +cmake_minimum_required(VERSION 3.0.2) +project(velocity_controller) + +## 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 + geometry_msgs + sensor_msgs + rplidar_ros +) + +## 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 +# geometry_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 velocity_controller +# CATKIN_DEPENDS geometry_msgs rplidar_ros +# 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}/velocity_controller.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/velocity_controller_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_velocity_controller.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/velocity_controller/nodes/velocity_controller.py b/velocity_controller/nodes/velocity_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..1b6a3f01c0af4d233fff816cad2191a9540f09b5 --- /dev/null +++ b/velocity_controller/nodes/velocity_controller.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 + +import rospy +from geometry_msgs.msg import Twist +from sensor_msgs.msg import LaserScan +from math import radians +import rospy + +EMERGENCY_DISTANCE = 0.22 # in meters +EMERGENCY_POINTS_COUNT_THRESHOLD = 2 + + +class VelocityController(): + + def __init__(self): + self.cmd_vel_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=5) + self.am_cmd_vel_sub = rospy.Subscriber( + "/actions_manager/cmd_vel", Twist, self.am_cmd_vel_cb) + self.scan_sub = rospy.Subscriber("/scan", LaserScan, self.scan_cb) + self.scan = None + + def scan_cb(self, scan): + self.scan = scan + + def am_cmd_vel_cb(self, cmd_vel): + """ + Receives the cmd_vel from the Action Manager and zeroes it if something is detecting with the LiDAR + """ + linear_speed = cmd_vel.linear.x + angular_speed = cmd_vel.angular.z + + linear_speed_cmd = linear_speed + angular_speed_cmd = angular_speed + + # First check if we can go in the wanted direction (ie. not obstacle in front/back if going forward/backward) + if self.scan is None: + linear_speed_cmd = 0 + angular_speed_cmd = 0 + else: + counter = 0 + + for i, distance in enumerate(self.scan.ranges): + angle = self.scan.angle_min + i * self.scan.angle_increment + + if distance < EMERGENCY_DISTANCE: + # Stop if we detected something in the front and we are going forward + if linear_speed > 0 and (radians(-180) < angle < radians(-45-90) or radians(45+90) < angle < radians(180)): + counter = counter + 1 + # Stop if we detected something in the back and we are going backward + elif linear_speed < 0 and radians(-45) < angle < radians(45): + counter = counter + 1 + + if counter >= EMERGENCY_POINTS_COUNT_THRESHOLD: + linear_speed_cmd = 0 + angular_speed_cmd = 0 + + twist = Twist() + twist.linear.x = linear_speed_cmd + twist.angular.z = angular_speed_cmd + self.cmd_vel_pub.publish(twist) + + +if __name__ == '__main__': + + rospy.init_node("velocity_controller") + + try: + VelocityController() + rospy.spin() + except rospy.ROSInterruptException: + pass diff --git a/velocity_controller/package.xml b/velocity_controller/package.xml new file mode 100644 index 0000000000000000000000000000000000000000..a04320af95757e55d7e7114c61596d58337237c1 --- /dev/null +++ b/velocity_controller/package.xml @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<package format="2"> + <name>velocity_controller</name> + <version>0.1.0</version> + <description>The velocity_controller package</description> + + <!-- One maintainer tag required, multiple allowed, one person per tag --> + <!-- Example: --> + <!-- <maintainer email="jane.doe@example.com">Jane Doe</maintainer> --> + <maintainer email="charles@todo.todo">charles</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>GPLv3</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/velocity_controller</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> --> + <author >Charles JAVERLIAT</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>geometry_msgs</build_depend> + <build_depend>rplidar_ros</build_depend> + <build_depend>sensor_msgs</build_depend> + <build_export_depend>geometry_msgs</build_export_depend> + <build_export_depend>rplidar_ros</build_export_depend> + <exec_depend>geometry_msgs</exec_depend> + <exec_depend>rplidar_ros</exec_depend> + <exec_depend>sensor_msgs</exec_depend> + + + <!-- The export tag contains other, unspecified, tags --> + <export> + <!-- Other tools can request additional information be placed here --> + + </export> +</package>