├── SFM.jpg
├── media
└── logos_repo_github.jpg
├── sfm_plugin.xml
├── cfg
├── local_planner.yaml
└── SFMLocalController.cfg
├── LICENSE
├── package.xml
├── CMakeLists.txt
├── include
└── sfm_local_controller
│ ├── sfm_controller_ros.h
│ ├── sensor_interface.h
│ ├── sfm_controller.h
│ └── collision_checker.h
├── README.md
└── src
├── sfm_controller_ros.cpp
├── collision_checker.cpp
├── sfm_controller.cpp
└── sensor_interface.cpp
/SFM.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robotics-upo/sfm_local_controller/HEAD/SFM.jpg
--------------------------------------------------------------------------------
/media/logos_repo_github.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robotics-upo/sfm_local_controller/HEAD/media/logos_repo_github.jpg
--------------------------------------------------------------------------------
/sfm_plugin.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | A implementation of a simple local controller to follow a pre-defined path.
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/cfg/local_planner.yaml:
--------------------------------------------------------------------------------
1 | base_local_planner: sfm_local_controller/SFMControllerROS
2 |
3 |
4 | SFMControllerROS:
5 |
6 | robot_frame: base_link
7 | planner_frame: odom
8 |
9 | max_lin_vel: 0.8
10 | min_lin_vel: 0.12
11 | max_rot_vel: !degrees 60.0
12 | min_rot_vel: !degrees 30.0
13 | min_rot_in_place: !degrees 40.0
14 | max_lin_acc: 0.15
15 | max_rot_acc: !degrees 30.0
16 | a: 5.0
17 |
18 | goal_tolerance: 0.2
19 | yaw_tolerance: 0.3
20 | local_goal_dist: 1.1
21 | robot_radius: 0.35
22 | people_radius: 0.35
23 |
24 | #sensor interface params
25 | laser_topic: scan_raw
26 | #people_topic:
27 | dyn_obs_topic: dynamic_obstacles
28 | odom_topic: mobile_base_controller/odom
29 | max_obstacle_dist: 3.0
30 | naive_goal_time: 1.0
31 | people_velocity: 1.9
32 |
33 | # sfm params
34 | sfm_goal_weight: 1.0
35 | sfm_obstacle_weight: 15.0
36 | sfm_people_weight: 8.0
37 |
38 | #collision checker
39 | use_laser: true
40 | sensor_uncertainty: 0.05
41 | use_range: false
42 |
--------------------------------------------------------------------------------
/cfg/SFMLocalController.cfg:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | PACKAGE = 'sfm_local_controller'
4 |
5 | from math import pi
6 |
7 | from dynamic_reconfigure.parameter_generator_catkin import *
8 |
9 | gen = ParameterGenerator()
10 |
11 |
12 | gen.add("max_lin_acc", double_t, 0, "The translational acceleration limit of the robot", 1.0, 0, 20.0)
13 | gen.add("max_rot_acc", double_t, 0, "The rotational acceleration limit of the robot", 1.0, 0, 20.0)
14 | gen.add("max_lin_vel", double_t, 0, "The maximum translation velocity for the robot in m/s", 0.6, 0, 10.0)
15 | gen.add("min_lin_vel", double_t, 0, "The minimum translation velocity for the robot in m/s", 0.1, 0, 10.0)
16 | gen.add("max_rot_vel", double_t, 0, "The absolute value of the maximum rotational velocity for the robot in rad/s", 0.8, 0, 20.0)
17 | gen.add("min_rot_vel", double_t, 0, "The absolute value of the minimum rotational velocity for the robot in rad/s", 0.3, 0, 20.0)
18 | #gen.add("min_in_place_rot_vel", double_t, 0, "The absolute value of the minimum in-place rotational velocity the controller will explore", 0.5, 0, 20.0)
19 |
20 | # sfm weights
21 | gen.add("sfm_goal_weight", double_t, 0, "Weight for the attraction force to the goal", 5.0, 0, 100.0)
22 | gen.add("sfm_obstacle_weight", double_t, 0, "Weight for the repulsion force to obstacles", 15.0, 0, 100.0)
23 | gen.add("sfm_people_weight", double_t, 0, "Weight for the repulsion force to people", 12.0, 0, 100.0)
24 |
25 |
26 | exit(gen.generate(PACKAGE, "sfm_local_controller", "SFMLocalController"))
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2021, Service Robotics Lab
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/package.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | sfm_local_controller
4 | 0.0.0
5 | The sfm_local_controller package
6 |
7 |
8 | Noé Pérez
9 |
10 |
11 | BSD
12 |
13 | catkin
14 | geometry_msgs
15 | lightsfm
16 | nav_core
17 | nav_msgs
18 | pluginlib
19 | roscpp
20 | sensor_msgs
21 | tf2
22 | tf2_geometry_msgs
23 | tf2_ros
24 | visualization_msgs
25 | dynamic_reconfigure
26 | dynamic_obstacle_detector
27 | geometry_msgs
28 | lightsfm
29 | nav_core
30 | nav_msgs
31 | pluginlib
32 | roscpp
33 | sensor_msgs
34 | tf2
35 | tf2_geometry_msgs
36 | tf2_ros
37 | visualization_msgs
38 | geometry_msgs
39 | lightsfm
40 | nav_core
41 | nav_msgs
42 | pluginlib
43 | roscpp
44 | sensor_msgs
45 | tf2
46 | tf2_geometry_msgs
47 | tf2_ros
48 | visualization_msgs
49 | dynamic_reconfigure
50 | dynamic_obstacle_detector
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.0.2)
2 | project(sfm_local_controller)
3 |
4 | ## Compile as C++11, supported in ROS Kinetic and newer
5 | # add_compile_options(-std=c++11)
6 |
7 | find_package(catkin REQUIRED COMPONENTS
8 | geometry_msgs
9 | dynamic_reconfigure
10 | nav_core
11 | nav_msgs
12 | pluginlib
13 | roscpp
14 | sensor_msgs
15 | tf2
16 | tf2_geometry_msgs
17 | tf2_ros
18 | visualization_msgs
19 | dynamic_obstacle_detector
20 | )
21 |
22 |
23 | ## Generate dynamic reconfigure parameters in the 'cfg' folder
24 | generate_dynamic_reconfigure_options(
25 | cfg/SFMLocalController.cfg
26 | )
27 |
28 | ###################################
29 | ## catkin specific configuration ##
30 | ###################################
31 | ## The catkin_package macro generates cmake config files for your package
32 | ## Declare things to be passed to dependent projects
33 | ## INCLUDE_DIRS: uncomment this if your package contains header files
34 | ## LIBRARIES: libraries you create in this project that dependent projects also need
35 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need
36 | ## DEPENDS: system dependencies of this project that dependent projects also need
37 | catkin_package(
38 | INCLUDE_DIRS include
39 | LIBRARIES
40 | sfm_local_controller
41 | sfm_controller_ros
42 | CATKIN_DEPENDS geometry_msgs lightsfm nav_core nav_msgs pluginlib roscpp sensor_msgs tf2 tf2_geometry_msgs tf2_ros visualization_msgs
43 | # DEPENDS system_lib
44 | )
45 |
46 | ###########
47 | ## Build ##
48 | ###########
49 |
50 | ## Specify additional locations of header files
51 | ## Your package locations should be listed before other locations
52 | include_directories(
53 | include
54 | /usr/local/include #to find lightsfm
55 | ${catkin_INCLUDE_DIRS}
56 | )
57 |
58 | ## Declare a C++ library
59 | add_library(${PROJECT_NAME}
60 | src/sensor_interface.cpp
61 | src/collision_checker.cpp
62 | src/sfm_controller.cpp
63 | )
64 |
65 | add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS} ${PROJECT_NAME}_gencfg)
66 | target_link_libraries(${PROJECT_NAME}
67 | ${catkin_LIBRARIES}
68 | )
69 |
70 | ## Declare a C++ library
71 | add_library(sfm_controller_ros
72 | src/sfm_controller_ros.cpp
73 | )
74 |
75 | add_dependencies(sfm_controller_ros ${sfm_controller_ros_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
76 | target_link_libraries(sfm_controller_ros
77 | ${PROJECT_NAME}
78 | ${catkin_LIBRARIES}
79 | )
80 |
81 |
82 | install(TARGETS
83 | ${PROJECT_NAME}
84 | sfm_controller_ros
85 | ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
86 | LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
87 | #RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
88 | )
89 |
90 | install(FILES sfm_plugin.xml
91 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
92 | )
93 |
94 | install(DIRECTORY include/${PROJECT_NAME}/
95 | DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION}
96 | PATTERN ".svn" EXCLUDE
97 | )
98 |
99 |
--------------------------------------------------------------------------------
/include/sfm_local_controller/sfm_controller_ros.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Implementation of the BaseLocalPlanner plugin
3 | * in order to use the sfm_local_controller in the
4 | * ROS move_base architecture.
5 | *
6 | * Author: Noé Pérez-Higueras
7 | * Service Robotics Lab, Pablo de Olavide University 2021
8 | *
9 | * Software License Agreement (BSD License)
10 | *
11 | */
12 |
13 | #ifndef SFM_CONTROLLER_ROS_H_
14 | #define SFM_CONTROLLER_ROS_H_
15 |
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | #include
24 | #include
25 | #include
26 | #include
27 |
28 | #include
29 | #include
30 |
31 | // Dynamic reconfigure
32 | #include
33 | #include
34 |
35 | #include
36 |
37 | namespace Upo {
38 | namespace Navigation {
39 | namespace sfm_controller {
40 | /**
41 | * @class SFMControllerROS
42 | * @brief A ROS wrapper for the sfm local controller that queries the param
43 | * server to construct a controller
44 | */
45 | class SFMControllerROS : public nav_core::BaseLocalPlanner {
46 | public:
47 | /**
48 | * @brief Default constructor for the ros wrapper
49 | */
50 | SFMControllerROS();
51 | /**
52 | * @brief Destructor for the wrapper
53 | */
54 | ~SFMControllerROS();
55 | /**
56 | * @brief Constructs the ros wrapper
57 | * @param name The name to give this instance of the trajectory planner
58 | * @param tf A pointer to a transform listener
59 | * @param costmap The cost map to use for assigning costs to trajectories. NOT
60 | * USED
61 | */
62 | SFMControllerROS(std::string name, tf2_ros::Buffer *tf,
63 | costmap_2d::Costmap2DROS *costmap_ros);
64 | /**
65 | * @brief Constructs the ros wrapper
66 | * @param name The name to give this instance of the trajectory planner
67 | * @param tf A pointer to a transform listener
68 | * @param costmap The cost map to use for assigning costs to trajectories. NOT
69 | * USED
70 | */
71 | void initialize(std::string name, tf2_ros::Buffer *tf,
72 | costmap_2d::Costmap2DROS *costmap_ros);
73 | /**
74 | * @brief Given the current position, orientation, and velocity of the robot,
75 | * compute velocity commands to send to the base
76 | * @param cmd_vel Will be filled with the velocity command to be passed to the
77 | * robot base
78 | * @return True if a valid trajectory was found, false otherwise
79 | */
80 | bool computeVelocityCommands(geometry_msgs::Twist &cmd_vel);
81 | /**
82 | * @brief Set the plan that the controller is following
83 | * @param orig_global_plan The plan to pass to the controller
84 | * @return True if the plan was updated successfully, false otherwise
85 | */
86 | bool setPlan(const std::vector &orig_global_plan);
87 |
88 | /**
89 | * @brief Check if the goal pose has been achieved
90 | * @return True if achieved, false otherwise
91 | */
92 | bool isGoalReached();
93 |
94 | // void scanCb(const sensor_msgs::LaserScan::ConstPtr &laser);
95 |
96 | private:
97 | /**
98 | * @brief Callback to update the local planner's parameters based on dynamic
99 | * reconfigure
100 | */
101 | void reconfigureCB(sfm_local_controller::SFMLocalControllerConfig &config,
102 | uint32_t level);
103 |
104 | std::vector
105 | transformPlan(const std::vector &plan,
106 | std::string frame);
107 |
108 | // ROS parameters
109 | tf2_ros::Buffer *tf_buffer_;
110 | std::string planner_frame_;
111 | std::string robot_frame_;
112 | ros::Publisher g_plan_pub_, l_plan_pub_;
113 |
114 | // ros::Subscriber odom_sub_;
115 | // ros::Subscriber laser_sub_, people_sub_, dyn_obs_sub_, sonar_sub_;
116 |
117 | std::vector planner_path_;
118 | bool goal_reached_;
119 |
120 | // dynamic reconfigure
121 | dynamic_reconfigure::Server
122 | *dsrv_;
123 | sfm_local_controller::SFMLocalControllerConfig default_config_;
124 |
125 | // Controller
126 | SFMController *sfm_controller_;
127 | SFMSensorInterface *sensor_iface_;
128 | CollisionChecker *collision_checker_;
129 | bool initialized_;
130 | };
131 |
132 | } // namespace sfm_controller
133 | } // namespace Navigation
134 | } // namespace Upo
135 |
136 | #endif
137 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # sfm_local_controller
2 | A robot local controller based on the Social Force Model (SFM) (https://github.com/robotics-upo/lightsfm).
3 | A set of attractive and repulsive forces determine the behavior of the planner.
4 |
5 | This local planner has been programmed as a BaseLocalPlanner plugin. So it can be used in the ROS move_base architecture.
6 | Although It receives a local costmap from move_base, the costmap is not used and a dedicated collision detector is employed instead. This detector uses laserscan input to check the possible collisions.
7 |
8 | 
9 |
10 |
11 | ## Acknowledgment
12 |
13 | This work has been financed by the European Regional Development Fund (FEDER) and by the Ministry of Economy, Knowledge, Business and University, of the Government of Andalucía , within the framework of the FEDER Andalucía 2014-2020 operational program. Specific objective 1.2.3. "Promotion and generation of frontier knowledge and knowledge oriented to the challenges of society, development of emerging technologies" within the framework of the reference research project UPO-1264631.
14 | FEDER co-financing percentage 80%
15 |
16 |
17 | ## Parameters
18 |
19 | * **Robot Configuration Parameters**
20 | - *max_lin_acc*. Maximum acceleration in translation (m/s^2).
21 | - *max_rot_acc*. Maximum acceleration in rotation (rad/s^2).
22 | - *max_lin_vel*. Maximum linear velocity (m/s).
23 | - *min_lin_vel*. Minimum linear velocity (m/s).
24 | - *max_rot_vel*. Maximum angular velocity (rad/s).
25 | - *min_rot_vel*. Minimum angular velocity (rad/s).
26 | - *min_rot_in_place*. Angular velocity of rotations in place (rad/s).
27 | - *a*. A factor that determines the angular velocity when the robot is turning. (Def: 10.0).
28 | - *planner_frame*. The frame in which the controller works. (Def: "odom").
29 | - *robot_frame*. The robot base frame. (Def: "base_link").
30 | - *robot_radius*. Radius (m) of the approximated circumference of the robot footprint.
31 | - *people_radius*. Radius (m) of the approximated circumference that circumscribe a human footprint. (Def: 0.35).
32 |
33 | * **Goal Tolerance Parameters**
34 | - *goal_tolerance*. Tolerance in euclidean distance (m) to consider that the goal has been reached.
35 | - *yaw_tolerance*. Tolerance in angular distance (rad) to consider that the goal has been reached.
36 | - *local_goal_dist*. Distance (m) from the robot to look for a local goal in the global path (Def: 1.0).
37 |
38 | * **Sensor Interface Parameters**
39 | The sensor interface is in charge of taking the sensory input and update the information of the surrounding social agents in the scene.
40 | - *laser_topic*. Topic in which the laser range finder is being published [sensor_msgs/LaserScan].
41 | - *people_topic*. Topic in which the people detected are being published [people_msgs/People].
42 | - *dyn_obs_topic*. Topic in which the dynamic obstacles detected are being published [dynamic_obstacle_detector/DynamicObstacles].
43 | - *odom_topic*. Odometry topic [nav_msgs/Odometry].
44 | - *max_obstacle_dist*. Maximum distance (m) in which the obstacles are considered for the social force model.
45 | - *naive_goal_time*. Lookahead time (secs) to predict an approximate goal for the pedestrians.
46 | - *people_velocity*. Average velocity of the pedestrians (m/s).
47 |
48 | * **Social Force Model Parameters**
49 | - *sfm_goal_weight*. Weight of the attraction force to the goal.
50 | - *sfm_obstacle_weight*. Weight of the respulsive force of the obstacles.
51 | - *sfm_people_weight*. Weight of the respulsive force of the pedestrians.
52 |
53 | * **Collision Checker Parameters**
54 | - *use_laser*. Boolean to indicate whether to use a laser scan for input. The scan topic indicated in the previous parameter *laser_topic* will be used. (Def: True).
55 | - *use_range*. Boolean to indicate whether to use sonar ranges for collision detection. If True, the topic names of the different sonars must be indicated by the parameters *range_topic_X* in which the *X* must be replaced by a number. eg: *range_topic_0*, range_topic_1*, etc. (Def: False).
56 | - *sensor_uncertainty*. Uncertainty or accurary of the sensory input (m). (Def: 0.05).
57 |
58 | ## Publications
59 |
60 | Besides the publications of the commands and paths according to the move_base structure. The planner publishes some RViz markers:
61 |
62 | - */sfm/markers/goal* [visualization_msgs/Marker]. Arrow with the local goal of the controller.
63 |
64 | - */sfm/markers/robot_forces* [visualization_msgs/MarkerArray]. Vectors with the different forces of the SFM.
65 |
66 | ## Dependencies
67 |
68 | - dynamic_obstacle_detector (https://github.com/robotics-upo/dynamic_obstacle_detector).
69 |
70 | - lightsfm (https://github.com/robotics-upo/lightsfm)
71 |
72 |
73 |
--------------------------------------------------------------------------------
/include/sfm_local_controller/sensor_interface.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Class to adapt the sensory input and detections to be used by the
3 | * sfm_local_controller
4 | *
5 | * Author: Noé Pérez-Higueras
6 | * Service Robotics Lab, Pablo de Olavide University 2021
7 | *
8 | * Software License Agreement (BSD License)
9 | *
10 | */
11 |
12 | #ifndef SFMCONTROLLER_SENSOR_H_
13 | #define SFMCONTROLLER_SENSOR_H_
14 |
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | // sensor input for obstacles
25 | #include
26 | // Detection input for social navigation
27 | #include
28 | #include
29 | // Social Force Model
30 | #include
31 | #include
32 | #include
33 |
34 | #include
35 | #include
36 |
37 | namespace Upo {
38 | namespace Navigation {
39 | namespace sfm_controller {
40 |
41 | class SFMSensorInterface {
42 | public:
43 | /**
44 | * @brief Default constructor
45 | * @param n pointer to a ros node handle to publish to topics
46 | * @param tfBuffer Pointer to tf2 buffer
47 | * @param robot_max_lin_speed the maximum robot linear speed [m/s]
48 | * @param robot_radius the radius of the robot circunference [m]
49 | * @param person_radius the approximated radius of the people body
50 | * @param robot_frame the coordinate frame of the robot base
51 | * @param odom_frame the coordinate frame of the robot odometry
52 | **/
53 | SFMSensorInterface(ros::NodeHandle *n, tf2_ros::Buffer *tfBuffer,
54 | float robot_max_lin_speed, float robot_radius,
55 | float person_radius, std::string robot_frame,
56 | std::string odom_frame);
57 |
58 | /**
59 | * @brief Destructor class
60 | */
61 | ~SFMSensorInterface();
62 |
63 | /**
64 | * @brief Callback to process the laser scan sensory input.
65 | * @param laser laserScan message to be processed
66 | */
67 | void laserCb(const sensor_msgs::LaserScan::ConstPtr &laser);
68 |
69 | /**
70 | * @brief Callback to process the people detected in the robot vecinity.
71 | * @param people message with the people to be processed
72 | */
73 | void peopleCb(const people_msgs::People::ConstPtr &people);
74 |
75 | /**
76 | * @brief Callback to process the moving obstacles detected in the robot
77 | * vecinity.
78 | * @param obs messages with the obstacles to be processed.
79 | */
80 | void dynamicObsCb(
81 | const dynamic_obstacle_detector::DynamicObstacles::ConstPtr &obs);
82 |
83 | /**
84 | * @brief Callback to process the odometry messages with the robot movement.
85 | * @param odom messages with the obstacles to be processed.
86 | */
87 | void odomCb(const nav_msgs::Odometry::ConstPtr &odom);
88 |
89 | /**
90 | * @brief Tranform a coordinate vector from one frame to another
91 | * @param vector coordinate vector in the origin frame
92 | * @param from string with the name of the origin frame
93 | * @param to string with the name of the target frame
94 | * @return coordinate vector in the target frame
95 | */
96 | geometry_msgs::Vector3 transformVector(geometry_msgs::Vector3 &vector,
97 | std::string from, std::string to);
98 |
99 | /**
100 | * @brief returns the vector of sfm agents
101 | * @return agents vector
102 | */
103 | std::vector getAgents();
104 |
105 | void setMaxLinVel(float max_lin_vel) {
106 | agents_mutex_.lock();
107 | agents_[0].desiredVelocity = max_lin_vel;
108 | agents_mutex_.unlock();
109 | }
110 |
111 | private:
112 | /**
113 | * @brief publish the transformed points in RViz
114 | * @param points vector with the coordinates of the points
115 | * @return none
116 | */
117 | void publish_obstacle_points(const std::vector &points);
118 |
119 | // void updateAgents();
120 |
121 | ros::NodeHandle *nh_; // Pointer to the node node handle
122 | ros::NodeHandle n_;
123 | tf2_ros::Buffer *tf_buffer_; // Pointer to the tfBuffer created in the node
124 |
125 | ros::Subscriber odom_sub_;
126 | std::mutex odom_mutex_;
127 | ros::Subscriber laser_sub_, people_sub_, dyn_obs_sub_, sonar_sub_;
128 | ros::Publisher points_pub_;
129 |
130 | std::vector agents_; // 0: robot, 1..: Others
131 | // sfm::Agent robot_agent_;
132 | std::mutex agents_mutex_;
133 | std::vector obstacles_;
134 |
135 | bool laser_received_;
136 | ros::Time last_laser_;
137 |
138 | people_msgs::People people_;
139 | std::mutex people_mutex_;
140 | dynamic_obstacle_detector::DynamicObstacles dyn_obs_;
141 | std::mutex obs_mutex_;
142 |
143 | float person_radius_;
144 | float naive_goal_time_;
145 | float people_velocity_;
146 |
147 | float max_obstacle_dist_;
148 |
149 | std::string robot_frame_;
150 | std::string odom_frame_;
151 |
152 | // bool use_static_map_;
153 | // sfm::RosMap *static_map_;
154 | };
155 |
156 | } // namespace sfm_controller
157 | } // namespace Navigation
158 | } // namespace Upo
159 |
160 | #endif
--------------------------------------------------------------------------------
/include/sfm_local_controller/sfm_controller.h:
--------------------------------------------------------------------------------
1 | /**
2 | * Local robot controller based on the Social Force Model
3 | *
4 | * Author: Noé Pérez-Higueras
5 | * Service Robotics Lab, Pablo de Olavide University 2021
6 | *
7 | * Software License Agreement (BSD License)
8 | *
9 | */
10 |
11 | #ifndef SFM_CONTROLLER_H_
12 | #define SFM_CONTROLLER_H_
13 |
14 | #include
15 | #include
16 | #include
17 |
18 | #include
19 | #include
20 | #include
21 |
22 | #include // for copy() and assign()
23 | #include // for back_inserter
24 |
25 | namespace Upo {
26 | namespace Navigation {
27 | namespace sfm_controller {
28 | class SFMController {
29 | public:
30 | /**
31 | * @brief Default constructor
32 | */
33 | SFMController();
34 | /**
35 | * @brief Constructor with parameters
36 | */
37 | SFMController(ros::NodeHandle *n, tf2_ros::Buffer *tfBuffer,
38 | SFMSensorInterface *sensor_iface, CollisionChecker *coll_check,
39 | float max_lin_vel, float min_lin_vel, float max_vel_theta,
40 | float min_vel_theta, float min_rot_in_place, float max_lin_acc,
41 | float max_theta_acc, float local_goal_dist,
42 | float sfm_goal_weight, float sfm_obstacle_weight,
43 | float sfm_people_weight, float robot_radius,
44 | float person_radius, float goal_tolerance, float yaw_tolerance,
45 | std::string robot_frame, std::string controller_frame, float a);
46 | /**
47 | * @brief Default destructor
48 | */
49 | ~SFMController();
50 |
51 | /**
52 | * @brief Callback to update the local planner's parameters based on dynamic
53 | * reconfigure
54 | */
55 | void reconfigure(sfm_local_controller::SFMLocalControllerConfig &cfg);
56 |
57 | /**
58 | * @brief method to update the scenario situation
59 | * @param path global path to be followed by the local controller
60 | * @return True when the goal has been reached, False otherwise
61 | */
62 | bool update(std::vector path);
63 | /**
64 | * @brief method to compute the velocity command of the robot
65 | * @param cmd_vel velocity message to be filled
66 | * @return True if a command vel was found
67 | */
68 | bool computeAction(geometry_msgs::Twist &cmd_vel);
69 |
70 | /**
71 | * @brief check if the current scenario leads to a possible collision
72 | * @return True if a possible collision was detected, False otherwise
73 | */
74 | bool fastCollisioncheck();
75 |
76 | private:
77 | /**
78 | * @brief inline method of normalize an angle
79 | * @param val the angle in rads to be normalized
80 | * @param min the min angle allowed
81 | * @param max the max angle allowed
82 | * @return the normalized angle value
83 | */
84 | float inline normalizeAngle(float val, float min, float max) {
85 | float norm = 0.0;
86 | if (val >= min)
87 | norm = min + fmod((val - min), (max - min));
88 | else
89 | norm = max - fmod((min - val), (max - min));
90 |
91 | return norm;
92 | }
93 | /**
94 | * @brief Compute an angular vel to be sent to the robot
95 | * @param max maximum angular vel allowed
96 | * @param exp_const parameter of the function
97 | * @param var the angle between the robot heading and the goal heading
98 | * @return the angular vel
99 | */
100 | float inline getVel(float max, float exp_const, float var) {
101 | return max * (1 - exp(-exp_const * fabs(var))) * var / fabs(var);
102 | };
103 | float a_;
104 |
105 | /**
106 | * @brief Publish an arrow marker in Rviz representing a force
107 | * @param index id of the marker
108 | * @param color RGB color of the marker
109 | * @param force force to be represented
110 | * @param markers markerArray in which the arrow will be added
111 | * @return none
112 | */
113 | void publishForceMarker(unsigned index, const std_msgs::ColorRGBA &color,
114 | const utils::Vector2d &force,
115 | visualization_msgs::MarkerArray &markers);
116 | /**
117 | * @brief Publish the set of SFM forces in RViz
118 | * @return none
119 | */
120 | void publishForces();
121 | /**
122 | * @brief fill a ColorRGBA message
123 | * @param r value of the red componet [0-1]
124 | * @param g value of the green component [0-1]
125 | * @param b value of the blue component [0-1]
126 | * @param a transparency of the color [0-1]
127 | * @return a ROS ColorRGBA message
128 | */
129 | std_msgs::ColorRGBA getColor(double r, double g, double b, double a);
130 |
131 | /**
132 | * @brief Publish in RViz the local goal followed by the controller
133 | * @param g goal position
134 | * @return none
135 | */
136 | void publishSFMGoal(const geometry_msgs::PoseStamped &g);
137 |
138 | SFMSensorInterface *sensor_iface_;
139 | CollisionChecker *collision_checker_;
140 |
141 | std::mutex configuration_mutex_;
142 |
143 | std::string planner_frame_;
144 | std::string robot_frame_;
145 | ros::Publisher g_plan_pub_, l_plan_pub_, robot_markers_pub_,
146 | sfm_goal_pub_; //, vel_pub_;
147 |
148 | sfm::Agent robot_;
149 | std::vector agents_;
150 | geometry_msgs::Pose goal_;
151 | std::vector planner_path_;
152 | bool goal_reached_;
153 | bool rotate_;
154 |
155 | ros::Time last_command_time_;
156 |
157 | // Robot configuration parameters
158 | float max_lin_vel_, min_lin_vel_;
159 | float max_vel_theta_, min_vel_theta_;
160 | float min_rot_in_place_;
161 | float max_lin_acc_;
162 | float max_theta_acc_;
163 | float robot_radius_;
164 | float person_radius_;
165 | float goal_tolerance_;
166 | float yaw_tolerance_;
167 | float local_goal_dist_;
168 |
169 | // SFM weights
170 | float sfm_goal_weight_;
171 | float sfm_obstacle_weight_;
172 | float sfm_people_weight_;
173 | }; // namespace sfm_controller
174 | } // namespace sfm_controller
175 | } // namespace Navigation
176 | } // namespace Upo
177 |
178 | #endif
--------------------------------------------------------------------------------
/include/sfm_local_controller/collision_checker.h:
--------------------------------------------------------------------------------
1 | #ifndef COLLISION_CHECKER_H_
2 | #define COLLISION_CHECKER_H_
3 |
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 |
23 | namespace Upo {
24 | namespace Navigation {
25 | namespace sfm_controller {
26 |
27 | class CollisionChecker {
28 |
29 | public:
30 | struct range {
31 | std::string id;
32 | float range;
33 | float polar_angle;
34 | float polar_dist;
35 | float fov;
36 | float min_dist;
37 | float max_dist;
38 | };
39 |
40 | CollisionChecker(ros::NodeHandle *n, tf2_ros::Buffer *tf, double max_lv,
41 | double max_av, double lin_acc, double ang_acc,
42 | double r_radius, std::string base_frame,
43 | std::string planner_frame);
44 |
45 | ~CollisionChecker();
46 |
47 | void setup();
48 |
49 | /**
50 | * @brief Generate and check a single trajectory
51 | * @param cvx The current x velocity of the robot
52 | * @param cvy The current y velocity of the robot
53 | * @param cvth The current angular velocity of the robot
54 | * @param tvx The x velocity used to seed the trajectory
55 | * @param tvy The y velocity used to seed the trajectory
56 | * @param tvth The theta velocity used to seed the trajectory
57 | * @param dt The time for project the movement
58 | * @return True if the trajectory is legal, false otherwise
59 | */
60 | bool checkCommand(double cvx, double cvy, double cvth, double tvx, double tvy,
61 | double tvth, double dt);
62 |
63 | // void saturateVelocities(geometry_msgs::Twist *twist);
64 |
65 | void setVelParams(double max_lin_vel, double max_vel_theta,
66 | double max_lin_acc, double max_theta_acc) {
67 | params_mutex_.lock();
68 | max_lin_vel_ = max_lin_vel;
69 | max_ang_vel_ = max_vel_theta;
70 | max_lin_acc_ = max_lin_acc;
71 | max_ang_acc_ = max_theta_acc;
72 | params_mutex_.unlock();
73 | }
74 |
75 | void laserCallback(const sensor_msgs::LaserScan::ConstPtr &msg);
76 |
77 | std::vector
78 | laser_polar2euclidean(const sensor_msgs::LaserScan *scan);
79 |
80 | void transformPoints(std::vector &points,
81 | std::string input_frame);
82 |
83 | void rangeCallback(const sensor_msgs::Range::ConstPtr &msg);
84 | void initiateRanges();
85 |
86 | bool inRangeCollision(float x, float y);
87 | bool inLaserCollision(float x, float y,
88 | const std::vector &scanpoints);
89 | bool inCollision(float x, float y,
90 | const std::vector &scanpoints);
91 |
92 | private:
93 | float inline normalizeAngle(float val, float min, float max) {
94 | float norm = 0.0;
95 | if (val >= min)
96 | norm = min + fmod((val - min), (max - min));
97 | else
98 | norm = max - fmod((min - val), (max - min));
99 |
100 | return norm;
101 | }
102 |
103 | /**
104 | * @brief Compute x position based on velocity
105 | * @param xi The current x position
106 | * @param vx The current x velocity
107 | * @param vy The current y velocity
108 | * @param theta The current orientation
109 | * @param dt The timestep to take
110 | * @return The new x position
111 | */
112 | inline double computeNewXPosition(double xi, double vx, double vy,
113 | double theta, double dt) {
114 | return xi + (vx * cos(theta) + vy * cos(M_PI_2 + theta)) * dt;
115 | }
116 |
117 | /**
118 | * @brief Compute y position based on velocity
119 | * @param yi The current y position
120 | * @param vx The current x velocity
121 | * @param vy The current y velocity
122 | * @param theta The current orientation
123 | * @param dt The timestep to take
124 | * @return The new y position
125 | */
126 | inline double computeNewYPosition(double yi, double vx, double vy,
127 | double theta, double dt) {
128 | return yi + (vx * sin(theta) + vy * sin(M_PI_2 + theta)) * dt;
129 | }
130 |
131 | /**
132 | * @brief Compute orientation based on velocity
133 | * @param thetai The current orientation
134 | * @param vth The current theta velocity
135 | * @param dt The timestep to take
136 | * @return The new orientation
137 | */
138 | inline double computeNewThetaPosition(double thetai, double vth, double dt) {
139 | return thetai + vth * dt;
140 | }
141 |
142 | // compute velocity based on acceleration
143 | /**
144 | * @brief Compute velocity based on acceleration
145 | * @param vg The desired velocity, what we're accelerating up to
146 | * @param vi The current velocity
147 | * @param a_max An acceleration limit
148 | * @param dt The timestep to take
149 | * @return The new velocity
150 | */
151 | inline double computeNewVelocity(double vg, double vi, double a_max,
152 | double dt) {
153 | if ((vg - vi) >= 0) {
154 | return std::min(vg, vi + a_max * dt);
155 | }
156 | return std::max(vg, vi - a_max * dt);
157 | }
158 |
159 | // tf::TransformListener* tf_;
160 | tf2_ros::Buffer *tf_;
161 |
162 | ros::NodeHandle *nh_; // Pointer to the node node handle
163 | ros::NodeHandle n_;
164 |
165 | std::string odom_topic_;
166 | std::string robot_frame_;
167 | std::string planner_frame_;
168 |
169 | double max_lin_vel_;
170 | double max_ang_vel_;
171 | double max_lin_acc_;
172 | double max_ang_acc_;
173 | // double sim_time_;
174 | double robot_radius_;
175 | double robot_radius_aug_;
176 | // double local_radius_;
177 | // double granularity_;
178 | std::mutex params_mutex_;
179 |
180 | // float max_lv_var_;
181 | // float max_av_var_;
182 |
183 | // tf::Stamped robot_vel_;
184 |
185 | // in case a laser range finder is also used
186 | bool use_laser_;
187 | ros::Subscriber laser_sub_;
188 | // sensor_msgs::LaserScan laser_scan_;
189 | std::vector scanpoints_;
190 | std::mutex laser_mutex_;
191 |
192 | // Sonar ranges employed
193 | bool use_range_;
194 | int num_ranges_;
195 | std::vector range_topics_;
196 | std::vector range_frames_;
197 | std::vector range_subscribers_;
198 | // ros::Subscriber range_sub_;
199 | std::vector ranges_;
200 | std::mutex range_mutex_;
201 | std::vector ranges_initiated_;
202 | bool ranges_ready_;
203 | };
204 |
205 | } // namespace sfm_controller
206 | } // namespace Navigation
207 | } // namespace Upo
208 | #endif
209 |
--------------------------------------------------------------------------------
/src/sfm_controller_ros.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * Implementation of the BaseLocalPlanner plugin
3 | * in order to use the sfm_local_controller in the
4 | * ROS move_base architecture.
5 | *
6 | * Author: Noé Pérez-Higueras
7 | * Service Robotics Lab, Pablo de Olavide University 2021
8 | *
9 | * Software License Agreement (BSD License)
10 | *
11 | */
12 |
13 | #include
14 |
15 | //#include
16 |
17 | //#include
18 |
19 | #include
20 | #include
21 |
22 | // register this planner as a BaseLocalPlanner plugin
23 | PLUGINLIB_EXPORT_CLASS(Upo::Navigation::sfm_controller::SFMControllerROS,
24 | nav_core::BaseLocalPlanner)
25 |
26 | namespace Upo {
27 | namespace Navigation {
28 | namespace sfm_controller {
29 |
30 | void SFMControllerROS::reconfigureCB(
31 | sfm_local_controller::SFMLocalControllerConfig &config, uint32_t level) {
32 |
33 | if (initialized_)
34 | sfm_controller_->reconfigure(config);
35 | }
36 |
37 | SFMControllerROS::SFMControllerROS()
38 | : initialized_(false), sfm_controller_(nullptr) {}
39 |
40 | SFMControllerROS::SFMControllerROS(std::string name, tf2_ros::Buffer *tf,
41 | costmap_2d::Costmap2DROS *costmap_ros)
42 | : initialized_(false), sfm_controller_(nullptr) {
43 | // initialize the planner
44 | initialize(name, tf, costmap_ros);
45 | }
46 |
47 | void SFMControllerROS::initialize(std::string name, tf2_ros::Buffer *tf,
48 | costmap_2d::Costmap2DROS *costmap_ros) {
49 | if (!initialized_) {
50 | ros::NodeHandle private_nh("~/" + name);
51 | g_plan_pub_ = private_nh.advertise("global_plan", 1);
52 | l_plan_pub_ = private_nh.advertise("local_plan", 1);
53 |
54 | tf_buffer_ = tf;
55 |
56 | // Frames
57 | private_nh.param("robot_frame", robot_frame_, std::string("base_link"));
58 | private_nh.param("planner_frame", planner_frame_, std::string("odom"));
59 |
60 | // Robot Configuration Parameters
61 | float max_lin_vel_, min_lin_vel_;
62 | float max_vel_theta_, min_vel_theta_;
63 | float min_rot_in_place_;
64 | float max_lin_acc_;
65 | float max_theta_acc_;
66 | private_nh.param("max_lin_vel", max_lin_vel_, float(0.6));
67 | private_nh.param("min_lin_vel", min_lin_vel_, float(0.1));
68 | private_nh.param("max_rot_vel", max_vel_theta_, float(0.5));
69 | private_nh.param("min_rot_vel", min_vel_theta_, float(0.1));
70 | private_nh.param("min_rot_in_place", min_rot_in_place_, float(0.35));
71 | private_nh.param("max_lin_acc", max_lin_acc_, float(1.0));
72 | private_nh.param("max_rot_acc", max_theta_acc_, float(1.0));
73 |
74 | // Dimensions
75 | float robot_radius;
76 | float people_radius;
77 | private_nh.param("robot_radius", robot_radius, float(0.35));
78 | private_nh.param("people_radius", people_radius, float(0.35));
79 | float robot_goal_dist;
80 | private_nh.param("local_goal_dist", robot_goal_dist, float(1.0));
81 |
82 | float a;
83 | private_nh.param("a", a, float(3.0));
84 |
85 | // Goal tolerance parameters
86 | float goal_tolerance, yaw_tolerance;
87 | private_nh.param("goal_tolerance", goal_tolerance, float(0.2));
88 | private_nh.param("yaw_tolerance", yaw_tolerance, float(0.35));
89 | // private_nh.param("xy_goal_tolerance", xy_goal_tolerance_, 0.10);
90 | // private_nh.param("wp_tolerance", wp_tolerance_, 0.5);
91 |
92 | // SFM weights
93 | float sfm_goal_weight_;
94 | float sfm_obstacle_weight_;
95 | float sfm_people_weight_;
96 | private_nh.param("sfm_goal_weight", sfm_goal_weight_, float(2.0));
97 | private_nh.param("sfm_obstacle_weight", sfm_obstacle_weight_, float(20.0));
98 | private_nh.param("sfm_people_weight", sfm_people_weight_, float(12.0));
99 |
100 | // std::string laser_topic;
101 | // private_nh.param("laser_topic", laser_topic, std::string("scan"));
102 | // std::string people_topic;
103 | // private_nh.param("people_topic", people_topic, std::string("people"));
104 | // std::string obs_topic;
105 | // private_nh.param("dyn_obs_topic", obs_topic, std::string("obstacles"));
106 | // std::string odom_topic;
107 | // private_nh.param("odom_topic", odom_topic, std::string("odom"));
108 |
109 | sensor_iface_ =
110 | new SFMSensorInterface(&private_nh, tf, max_lin_vel_, robot_radius,
111 | people_radius, robot_frame_, planner_frame_);
112 |
113 | collision_checker_ = new CollisionChecker(
114 | &private_nh, tf, max_lin_vel_, max_vel_theta_, max_lin_acc_,
115 | max_theta_acc_, robot_radius, robot_frame_, planner_frame_);
116 |
117 | // ros::NodeHandle n;
118 | // laser_sub_ = n.subscribe(
119 | // laser_topic.c_str(), 1, &SFMSensorInterface::laserCb, sensor_iface_);
120 |
121 | // people_sub_ = n.subscribe(
122 | // people_topic.c_str(), 1, &SFMSensorInterface::peopleCb,
123 | // sensor_iface_);
124 |
125 | // dyn_obs_sub_ = n.subscribe(
126 | // obs_topic.c_str(), 1, &SFMSensorInterface::dynamicObsCb,
127 | // sensor_iface_);
128 |
129 | // odom_sub_ = n.subscribe(
130 | // odom_topic.c_str(), 1, &SFMSensorInterface::odomCb, sensor_iface_);
131 |
132 | // std::cout << std::endl
133 | // << "SFM SENSOR INTERFACE:" << std::endl
134 | // << "laser_topic: " << laser_topic << std::endl
135 | // << "people_topic: " << people_topic << std::endl
136 | // << "dyn_obs_topic: " << obs_topic << std::endl
137 | // << "odom_topic: " << odom_topic
138 | // << std::endl
139 | // //<< "max_obstacle_dist: " << max_obstacle_dist_ << std::endl
140 | // //<< "naive_goal_time: " << naive_goal_time_ << std::endl
141 | // //<< "people_velocity: " << people_velocity_ << std::endl
142 | // << std::endl;
143 |
144 | sfm_controller_ = new SFMController(
145 | &private_nh, tf, sensor_iface_, collision_checker_, max_lin_vel_,
146 | min_lin_vel_, max_vel_theta_, min_vel_theta_, min_rot_in_place_,
147 | max_lin_acc_, max_theta_acc_, robot_goal_dist, sfm_goal_weight_,
148 | sfm_obstacle_weight_, sfm_people_weight_, robot_radius, people_radius,
149 | goal_tolerance, yaw_tolerance, robot_frame_, planner_frame_, a);
150 |
151 | // BE CAREFUL, this will load the values of cfg params
152 | // overwritting the read ones from the yaml file (if planner already
153 | // initialized).
154 | dsrv_ = new dynamic_reconfigure::Server<
155 | sfm_local_controller::SFMLocalControllerConfig>(private_nh);
156 | dynamic_reconfigure::Server<
157 | sfm_local_controller::SFMLocalControllerConfig>::CallbackType cb =
158 | boost::bind(&SFMControllerROS::reconfigureCB, this, _1, _2);
159 | dsrv_->setCallback(cb);
160 |
161 | initialized_ = true;
162 | goal_reached_ = false;
163 |
164 | } else {
165 | ROS_WARN("This planner has already been initialized, doing nothing");
166 | }
167 | }
168 |
169 | SFMControllerROS::~SFMControllerROS() {
170 | // make sure to clean things up
171 | delete dsrv_;
172 |
173 | if (sfm_controller_ != nullptr)
174 | delete sfm_controller_;
175 | }
176 |
177 | bool SFMControllerROS::isGoalReached() { return goal_reached_; }
178 |
179 | bool SFMControllerROS::setPlan(
180 | const std::vector &orig_global_plan) {
181 | if (!initialized_) {
182 | ROS_ERROR("This planner has not been initialized, please call initialize() "
183 | "before using this local planner");
184 | return false;
185 | }
186 |
187 | planner_path_.clear();
188 | // Transform plan to planner_frame_ if necessary
189 | if (!orig_global_plan.empty()) {
190 | // if (orig_global_plan[0].header.frame_id != planner_frame_) {
191 | // planner_path_ = transformPlan(orig_global_plan, planner_frame_);
192 | //} else {
193 | planner_path_ = orig_global_plan;
194 | // planner_path_ = transformPlan(planner_path_, planner_frame_);
195 | //}
196 | }
197 | // reset the goal flag
198 | goal_reached_ = false;
199 |
200 | return true;
201 | }
202 |
203 | std::vector SFMControllerROS::transformPlan(
204 | const std::vector &plan, std::string frame) {
205 | std::vector planner_plan;
206 | for (unsigned int i = 0; i < plan.size(); i++) {
207 | try {
208 | geometry_msgs::PoseStamped pose = tf_buffer_->transform(plan[i], frame);
209 | planner_plan.push_back(pose);
210 | } catch (tf2::TransformException &ex) {
211 | ROS_WARN("Could NOT transform plan pose to %s: %s", frame.c_str(),
212 | ex.what());
213 | break;
214 | }
215 | }
216 | return planner_plan;
217 | }
218 |
219 | bool SFMControllerROS::computeVelocityCommands(geometry_msgs::Twist &cmd_vel) {
220 | if (!initialized_) {
221 | ROS_ERROR("This planner has not been initialized, please call initialize() "
222 | "before using this planner");
223 | return false;
224 | }
225 |
226 | // if the global plan passed in is empty... we won't do anything
227 | if (planner_path_.empty()) {
228 | ROS_WARN("Planner path is empty. Not computing cmd vels!!!!");
229 | return false;
230 | }
231 |
232 | planner_path_ = transformPlan(planner_path_, planner_frame_);
233 | goal_reached_ = sfm_controller_->update(planner_path_);
234 |
235 | geometry_msgs::Twist drive_cmds;
236 | if (goal_reached_) {
237 | ROS_INFO("GOAL REACHED!\n\n");
238 | cmd_vel = drive_cmds;
239 | cmd_vel.linear.x = 0.0;
240 | cmd_vel.linear.y = 0.0;
241 | cmd_vel.angular.z = 0.0;
242 | return true;
243 | }
244 | // compute what trajectory to drive along
245 | bool ok = sfm_controller_->computeAction(drive_cmds);
246 | // pass along drive commands
247 | cmd_vel = drive_cmds;
248 | if (!ok) {
249 | ROS_DEBUG_NAMED("trajectory_planner_ros",
250 | "The rollout planner failed to find a valid plan. This "
251 | "means that the footprint of the robot was in collision "
252 | "for all simulated trajectories.");
253 | // publishPlan(transformed_plan, g_plan_pub_);
254 | return false;
255 | }
256 | // publish information to the visualizer
257 | // publishPlan(transformed_plan, g_plan_pub_);
258 |
259 | return true;
260 | }
261 |
262 | } // namespace sfm_controller
263 | } // namespace Navigation
264 | } // namespace Upo
--------------------------------------------------------------------------------
/src/collision_checker.cpp:
--------------------------------------------------------------------------------
1 |
2 | #include
3 |
4 | namespace Upo {
5 | namespace Navigation {
6 | namespace sfm_controller {
7 |
8 | CollisionChecker::CollisionChecker(ros::NodeHandle *n, tf2_ros::Buffer *tf,
9 | double max_lv, double max_av, double lin_acc,
10 | double ang_acc, double r_radius,
11 | std::string base_frame,
12 | std::string planner_frame)
13 | : nh_(n), n_(), tf_(tf), max_lin_vel_(max_lv), max_ang_vel_(max_av),
14 | max_lin_acc_(lin_acc), max_ang_acc_(ang_acc), robot_radius_(r_radius),
15 | robot_frame_(base_frame), planner_frame_(planner_frame) {
16 |
17 | robot_radius_aug_ = robot_radius_;
18 |
19 | use_laser_ = true;
20 | use_range_ = false;
21 | num_ranges_ = 0;
22 | ranges_ready_ = false;
23 |
24 | setup();
25 | }
26 |
27 | CollisionChecker::~CollisionChecker() {}
28 |
29 | void CollisionChecker::setup() {
30 |
31 | double uncertainty;
32 | nh_->param("sensor_uncertainty", uncertainty, 0.05);
33 | robot_radius_aug_ = robot_radius_ + uncertainty;
34 |
35 | nh_->param("use_laser", use_laser_, true);
36 | std::string laser_topic;
37 | if (use_laser_) {
38 | nh_->param("laser_topic", laser_topic, std::string("scan"));
39 | laser_sub_ = n_.subscribe(
40 | laser_topic.c_str(), 1, &CollisionChecker::laserCallback, this);
41 | }
42 |
43 | unsigned int i = 0;
44 | nh_->param("use_range", use_range_, false);
45 | if (use_range_) {
46 | bool ok = true;
47 | while (ok) {
48 | char buf[25];
49 | sprintf(buf, "range_topic_%u", i);
50 | std::string st = std::string(buf);
51 |
52 | if (nh_->hasParam(st.c_str())) {
53 | std::string rt;
54 | nh_->getParam(st.c_str(), rt);
55 | range_topics_.push_back(rt);
56 | // ros::Subscriber sub = nh.subscribe(rt.c_str(),
57 | // 10, &CollisionDetection::rangeCallback, this);
58 | ros::Subscriber sub = n_.subscribe(
59 | rt.c_str(), 1,
60 | boost::bind(&CollisionChecker::rangeCallback, this, _1));
61 | range_subscribers_.push_back(sub);
62 | printf("%s. subscribed to topic: %s\n",
63 | ros::this_node::getName().c_str(), rt.c_str());
64 | i++;
65 | } else
66 | ok = false;
67 | }
68 | num_ranges_ = (int)i;
69 | ranges_initiated_.assign(num_ranges_, false);
70 | // range_frames_.assign(num_ranges_, "");
71 | }
72 | }
73 |
74 | void CollisionChecker::laserCallback(
75 | const sensor_msgs::LaserScan::ConstPtr &msg) {
76 | ROS_INFO_ONCE("CollisionChecker: Laser received!");
77 | // IMPORTANT: the frame of the laser should be the center of the robot
78 | // (base_link) Otherwise we should include a shift to the center in the
79 | // calculations.
80 |
81 | std::vector points = laser_polar2euclidean(msg.get());
82 | if (msg->header.frame_id != robot_frame_)
83 | transformPoints(points, msg->header.frame_id);
84 |
85 | laser_mutex_.lock();
86 | scanpoints_ = points;
87 | laser_mutex_.unlock();
88 | }
89 |
90 | std::vector
91 | CollisionChecker::laser_polar2euclidean(const sensor_msgs::LaserScan *scan) {
92 | std::vector points;
93 | points.reserve(scan->ranges.size());
94 | geometry_msgs::Point p;
95 | p.z = 0.0;
96 | for (unsigned int i = 0; i < scan->ranges.size(); i++) {
97 | // laser measure polar coordinates
98 | float laser_d = scan->ranges[i];
99 | float laser_th = scan->angle_min + scan->angle_increment * i;
100 | // transform to x,y
101 | p.x = laser_d * cos(laser_th);
102 | p.y = laser_d * sin(laser_th);
103 | points.push_back(p);
104 | }
105 | return points;
106 | }
107 |
108 | void CollisionChecker::transformPoints(
109 | std::vector &points, std::string input_frame) {
110 |
111 | // Transform points to
112 | // robot_frame_ if necessary
113 | geometry_msgs::PointStamped out;
114 | for (unsigned int i = 0; i < points.size(); i++) {
115 | geometry_msgs::PointStamped in;
116 | in.header.frame_id = input_frame;
117 | in.header.stamp = ros::Time(0);
118 | in.point.x = points[i].x;
119 | in.point.y = points[i].y;
120 | in.point.z = points[i].z;
121 | try {
122 | out = tf_->transform(in, robot_frame_);
123 | points[i].x = out.point.x;
124 | points[i].y = out.point.y;
125 | points[i].z = out.point.z;
126 |
127 | } catch (tf2::TransformException &ex) {
128 | ROS_WARN("[%s] Could NOT transform "
129 | "laser point to %s: "
130 | "%s",
131 | ros::this_node::getName(), robot_frame_.c_str(), ex.what());
132 | return;
133 | }
134 | }
135 | }
136 |
137 | void CollisionChecker::rangeCallback(const sensor_msgs::Range::ConstPtr &msg) {
138 | ROS_INFO_ONCE(
139 | "Collision detector: range received! Detecting configuration...");
140 | if (!ranges_ready_) {
141 | if (range_frames_.size() != (unsigned int)num_ranges_) {
142 | bool found = false;
143 | for (unsigned int i = 0; i < range_frames_.size(); i++) {
144 | if (range_frames_[i].compare(msg->header.frame_id) == 0) {
145 | found = true;
146 | break;
147 | }
148 | }
149 | if (!found) {
150 | range_frames_.push_back(msg->header.frame_id);
151 |
152 | range r;
153 | r.range = msg->range;
154 | r.id = msg->header.frame_id;
155 | r.min_dist = msg->min_range;
156 | r.max_dist = msg->max_range;
157 | r.fov = msg->field_of_view;
158 | // listen to the TF to know the position of the sonar range
159 | // tf::StampedTransform transform;
160 | geometry_msgs::TransformStamped ts;
161 | try {
162 | // tf_->waitForTransform(base_frame_.c_str(), r.id.c_str(),
163 | // ros::Time(0),
164 | // ros::Duration(3.0));
165 | // tf_->lookupTransform(base_frame_.c_str(), r.id.c_str(),
166 | // ros::Time(0), transform);
167 | ts = tf_->lookupTransform(robot_frame_.c_str(), r.id.c_str(),
168 | ros::Time(0));
169 | } catch (tf2::TransformException &ex) {
170 | ROS_ERROR("%s", ex.what());
171 | }
172 | float x = ts.transform.translation.x;
173 | float y = ts.transform.translation.y;
174 | r.polar_dist = sqrt(x * x + y * y);
175 | // tf::Matrix3x3 m(ts.transform.rotation);
176 | // double roll, pitch, yaw;
177 | // m.getRPY(roll, pitch, yaw);
178 | tf2::Quaternion quat_tf;
179 | tf2::fromMsg(ts.transform.rotation, quat_tf);
180 | r.polar_angle = tf2::impl::getYaw(quat_tf);
181 |
182 | range_mutex_.lock();
183 | ranges_.push_back(r);
184 | range_mutex_.unlock();
185 | printf("Collision detector: Range %s configured.\n",
186 | msg->header.frame_id.c_str());
187 | }
188 | } else {
189 | ranges_ready_ = true;
190 | printf("Collision detector: all the range sensors configured!!!\n\n");
191 | }
192 | } else {
193 | range_mutex_.lock();
194 | for (int i = 0; i < (int)ranges_.size(); i++) {
195 | if (ranges_[i].id.compare(msg->header.frame_id) == 0) {
196 | ranges_[i].range = msg->range;
197 | break;
198 | }
199 | }
200 | range_mutex_.unlock();
201 | }
202 | }
203 |
204 | /*
205 | void CollisionChecker::saturateVelocities(geometry_msgs::Twist *twist) {
206 | float lv = twist->linear.x;
207 | float av = twist->angular.z;
208 |
209 | float rvx = robot_vel_.getOrigin().getX();
210 | float rvy = robot_vel_.getOrigin().getY();
211 | float rvt = tf::getYaw(robot_vel_.getRotation());
212 |
213 | // acc linear
214 | if (fabs(rvx - lv) > max_lv_var_) {
215 | lv = (lv < rvx) ? (rvx - max_lv_var_) : (rvx + max_lv_var_);
216 | }
217 | // acc angular
218 | if (fabs(rvt - av) > max_av_var_) {
219 | av = (av < rvt) ? (rvt - max_av_var_) : (rvt + max_av_var_);
220 | }
221 |
222 | // Check maximum velocities
223 | if (lv > max_lin_vel_)
224 | lv = max_lin_vel_;
225 | else if (lv < (-max_lin_vel_))
226 | lv = max_lin_vel_ * (-1);
227 |
228 | if (av > max_ang_vel_)
229 | av = max_ang_vel_;
230 | else if (av < (-max_ang_vel_))
231 | av = max_ang_vel_ * (-1);
232 |
233 | twist->linear.x = lv;
234 | twist->angular.z = av;
235 | }
236 | */
237 |
238 | bool CollisionChecker::inCollision(
239 | float x, float y, const std::vector &scanpoints) {
240 | if (use_range_ && inRangeCollision(x, y)) {
241 | printf("Possible collision detected!");
242 | return true;
243 | }
244 | if (use_laser_ && inLaserCollision(x, y, scanpoints)) {
245 | return true;
246 | }
247 | return false;
248 | }
249 |
250 | bool CollisionChecker::inRangeCollision(float x, float y) {
251 | range_mutex_.lock();
252 | for (unsigned int i = 0; i < ranges_.size(); i++) {
253 | // Main point
254 | float rx = (ranges_[i].polar_dist + ranges_[i].range) *
255 | cos(ranges_[i].polar_angle);
256 | float ry = (ranges_[i].polar_dist + ranges_[i].range) *
257 | sin(ranges_[i].polar_angle);
258 | float dx = (x - rx);
259 | float dy = (y - ry);
260 | float dist = dx * dx + dy * dy;
261 | if (dist <= (robot_radius_aug_ * robot_radius_aug_)) {
262 | ROS_INFO("POSSIBLE COLLISION DETECTED IN FRAME: %s",
263 | ranges_[i].id.c_str());
264 | range_mutex_.unlock();
265 | return true;
266 | }
267 |
268 | if (ranges_[i].fov > 0.2) {
269 | // second point
270 | rx = (ranges_[i].polar_dist + ranges_[i].range) *
271 | cos(ranges_[i].polar_angle + (ranges_[i].fov / 2.0));
272 | ry = (ranges_[i].polar_dist + ranges_[i].range) *
273 | sin(ranges_[i].polar_angle + (ranges_[i].fov / 2.0));
274 | dx = (x - rx);
275 | dy = (y - ry);
276 | dist = dx * dx + dy * dy;
277 | if (dist <= (robot_radius_aug_ * robot_radius_aug_)) {
278 | ROS_INFO("POSSIBLE COLLISION DETECTED (p2) IN FRAME: %s",
279 | ranges_[i].id.c_str());
280 | range_mutex_.unlock();
281 | return true;
282 | }
283 |
284 | // third point
285 | rx = (ranges_[i].polar_dist + ranges_[i].range) *
286 | cos(ranges_[i].polar_angle - (ranges_[i].fov / 2.0));
287 | ry = (ranges_[i].polar_dist + ranges_[i].range) *
288 | sin(ranges_[i].polar_angle - (ranges_[i].fov / 2.0));
289 | dx = (x - rx);
290 | dy = (y - ry);
291 | dist = dx * dx + dy * dy;
292 | if (dist <= (robot_radius_aug_ * robot_radius_aug_)) {
293 | ROS_INFO("POSSIBLE COLLISION DETECTED (p3) IN FRAME: %s",
294 | ranges_[i].id.c_str());
295 | range_mutex_.unlock();
296 | return true;
297 | }
298 | }
299 | }
300 | range_mutex_.unlock();
301 | return false;
302 | }
303 |
304 | bool CollisionChecker::inLaserCollision(
305 | float x, float y, const std::vector &scanpoints) {
306 | for (unsigned int i = 0; i < scanpoints.size(); i++) {
307 | float dx = (x - scanpoints[i].x);
308 | float dy = (y - scanpoints[i].y);
309 | // float dist = sqrt(dx*dx + dy*dy);
310 | // if(dist <= robot_radius_)
311 | float dist = dx * dx + dy * dy;
312 | if (dist <= (robot_radius_aug_ * robot_radius_aug_))
313 | return true;
314 | }
315 | return false;
316 | }
317 |
318 | /**
319 | * @brief Generate and check a single trajectory
320 | * @param cvx The current x velocity of the robot
321 | * @param cvy The current y velocity of the robot
322 | * @param cvth The current angular velocity of the robot
323 | * @param tvx The x velocity used to seed the trajectory
324 | * @param tvy The y velocity used to seed the trajectory
325 | * @param tvth The theta velocity used to seed the trajectory
326 | * @param dt The time for project the movement
327 | * @return True if the trajectory is legal, false otherwise
328 | */
329 | bool CollisionChecker::checkCommand(double cvx, double cvy, double cvth,
330 | double tvx, double tvy, double tvth,
331 | double dt) {
332 |
333 | // printf("\nCollisionDetection. CheckCommand with vels vx:%.2f, vy:%.2f
334 | // th:%.2f\n", tvx, tvy, tvth);
335 |
336 | // double max_lv = computeNewVelocity(tvx, cvx, max_lin_acc_, sim_time_);
337 | // float vel_mag = sqrt(max_lv * max_lv);
338 | // float steps = (vel_mag * sim_time_) / granularity_;
339 | // float dt = sim_time_ / steps;
340 | float x = 0.0, y = 0.0, th = 0.0;
341 |
342 | double lv = cvx;
343 | double av = cvth;
344 |
345 | // geometry_msgs::PoseStamped pose;
346 | // pose.header.stamp = ros::Time(); // ros::Time::now();
347 | // pose.header.frame_id = features_->getRobotBaseFrame(); // base_frame_;
348 |
349 | std::vector laser_points;
350 | if (use_laser_) {
351 | laser_mutex_.lock();
352 | laser_points = scanpoints_;
353 | laser_mutex_.unlock();
354 | }
355 |
356 | // if dt is higher than 0.1 secs, split it in steps
357 | params_mutex_.lock();
358 | double step = 0.1;
359 | int steps = std::ceil(dt / step);
360 | for (unsigned int i = 0; i < steps; i++) {
361 | lv = computeNewVelocity(tvx, lv, max_lin_acc_, step);
362 | av = computeNewVelocity(tvth, av, max_ang_acc_, step);
363 |
364 | // x = computeNewXPosition(x, lv, 0.0, av, step);
365 | // y = computeNewYPosition(y, lv, 0.0, av, step);
366 | // th = computeNewThetaPosition(th, av, step);
367 |
368 | float lin_dist = lv * step;
369 | th = th + (av * step);
370 | // normalization just in case
371 | th = normalizeAngle(th, -M_PI, M_PI);
372 | x = x + lin_dist * cos(th); // cos(th+av*dt/2.0)
373 | y = y + lin_dist * sin(th);
374 |
375 | if (inCollision(x, y, laser_points)) {
376 | params_mutex_.unlock();
377 | return false;
378 | }
379 | }
380 |
381 | // Now we check that we can stop without colliding
382 | while (lv > 0.0) {
383 | lv = computeNewVelocity(0.0, lv, max_lin_acc_, step);
384 | av = computeNewVelocity(0.0, av, max_ang_acc_, step);
385 |
386 | // x = computeNewXPosition(x, lv, 0.0, av, step);
387 | // y = computeNewYPosition(y, lv, 0.0, av, step);
388 | // th = computeNewThetaPosition(th, av, step);
389 |
390 | float lin_dist = lv * step;
391 | th = th + (av * step);
392 | // normalization just in case
393 | th = normalizeAngle(th, -M_PI, M_PI);
394 | x = x + lin_dist * cos(th); // cos(th+av*dt/2.0)
395 | y = y + lin_dist * sin(th);
396 |
397 | if (inCollision(x, y, laser_points)) {
398 | params_mutex_.unlock();
399 | return false;
400 | }
401 | }
402 | params_mutex_.unlock();
403 | return true;
404 | }
405 |
406 | } // namespace sfm_controller
407 | } // namespace Navigation
408 | } // namespace Upo
409 |
--------------------------------------------------------------------------------
/src/sfm_controller.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * Local robot controller based on the Social Force Model
3 | *
4 | * Author: Noé Pérez-Higueras
5 | * Service Robotics Lab, Pablo de Olavide University 2021
6 | *
7 | * Software License Agreement (BSD License)
8 | *
9 | */
10 |
11 | #include
12 |
13 | namespace Upo {
14 | namespace Navigation {
15 | namespace sfm_controller {
16 |
17 | /**
18 | * @brief Default constructor
19 | */
20 | SFMController::SFMController()
21 | : max_lin_vel_(0.7), min_lin_vel_(0.1), max_vel_theta_(0.6),
22 | min_vel_theta_(0.1), min_rot_in_place_(0.4), max_lin_acc_(1.0),
23 | max_theta_acc_(1.0), local_goal_dist_(1.0), sfm_goal_weight_(2.0),
24 | sfm_obstacle_weight_(10.0), sfm_people_weight_(12.0), robot_radius_(0.35),
25 | person_radius_(0.35), goal_tolerance_(0.15), yaw_tolerance_(0.3),
26 | robot_frame_(std::string("base_link")),
27 | planner_frame_(std::string("odom")), a_(2.0) {}
28 |
29 | /**
30 | * @brief Constructor with parameters
31 | */
32 | SFMController::SFMController(
33 | ros::NodeHandle *n, tf2_ros::Buffer *tfBuffer,
34 | SFMSensorInterface *sensor_iface, CollisionChecker *coll_check,
35 | float max_lin_vel, float min_lin_vel, float max_vel_theta,
36 | float min_vel_theta, float min_rot_in_place, float max_lin_acc,
37 | float max_theta_acc, float local_goal_dist, float sfm_goal_weight,
38 | float sfm_obstacle_weight, float sfm_people_weight, float robot_radius,
39 | float person_radius, float goal_tolerance, float yaw_tolerance,
40 | std::string robot_frame, std::string controller_frame, float a)
41 | : max_lin_vel_(max_lin_vel), min_lin_vel_(min_lin_vel),
42 | max_vel_theta_(max_vel_theta), min_vel_theta_(min_vel_theta),
43 | min_rot_in_place_(min_rot_in_place), max_lin_acc_(max_lin_acc),
44 | max_theta_acc_(max_theta_acc), local_goal_dist_(local_goal_dist),
45 | sfm_goal_weight_(sfm_goal_weight),
46 | sfm_obstacle_weight_(sfm_obstacle_weight),
47 | sfm_people_weight_(sfm_people_weight), robot_radius_(robot_radius),
48 | person_radius_(person_radius), goal_tolerance_(goal_tolerance),
49 | yaw_tolerance_(yaw_tolerance), robot_frame_(robot_frame),
50 | planner_frame_(controller_frame), a_(a) {
51 | // Initialize robot agent
52 | robot_.desiredVelocity = max_lin_vel_;
53 | robot_.radius = robot_radius_;
54 | robot_.cyclicGoals = false;
55 | robot_.teleoperated = true;
56 | robot_.params.forceFactorDesired = sfm_goal_weight_;
57 | robot_.params.forceFactorObstacle = sfm_obstacle_weight_;
58 | robot_.params.forceFactorSocial = sfm_people_weight_;
59 |
60 | std::cout << std::endl
61 | << "SFM_CONTROLLER INITIATED:" << std::endl
62 | << "max_lin_acc: " << max_lin_acc_ << std::endl
63 | << "max_rot_acc: " << max_theta_acc_ << std::endl
64 | << "min_lin_vel: " << min_lin_vel_ << std::endl
65 | << "max_lin_vel: " << max_lin_vel_ << std::endl
66 | << "min_rot_vel: " << min_vel_theta_ << std::endl
67 | << "max_rot_vel: " << max_vel_theta_ << std::endl
68 | << "local_goal_dist: " << local_goal_dist_ << std::endl
69 | << "goal_tolerance: " << goal_tolerance_ << std::endl
70 | << "robot_radius: " << robot_radius_ << std::endl
71 | << "person_radius: " << person_radius_ << std::endl
72 | << "robot_frame: " << robot_frame_ << std::endl
73 | << "planner_frame: " << planner_frame_ << std::endl
74 | << "sfm_goal_weight: " << sfm_goal_weight_ << std::endl
75 | << "sfm_obstacle_weight: " << sfm_obstacle_weight_ << std::endl
76 | << "sfm_social_weight: " << sfm_people_weight_ << std::endl
77 | << std::endl;
78 |
79 | // Advertise SFM related markers
80 | robot_markers_pub_ = n->advertise(
81 | "/sfm/markers/robot_forces", 1);
82 |
83 | // Adverstise SFM local goal
84 | sfm_goal_pub_ =
85 | n->advertise("/sfm/markers/goal", 1);
86 |
87 | // Initialize sensor interface
88 | sensor_iface_ = sensor_iface;
89 |
90 | // Initialize collision checker
91 | collision_checker_ = coll_check;
92 |
93 | last_command_time_ = ros::Time::now();
94 | goal_reached_ = false;
95 | rotate_ = false;
96 | }
97 |
98 | /**
99 | * @brief Default destructor
100 | */
101 | SFMController::~SFMController() {
102 | delete sensor_iface_;
103 | delete collision_checker_;
104 | }
105 |
106 | /**
107 | * @brief Callback to update the local planner's parameters based on dynamic
108 | * reconfigure
109 | */
110 | void SFMController::reconfigure(
111 | sfm_local_controller::SFMLocalControllerConfig &cfg) {
112 | sfm_local_controller::SFMLocalControllerConfig config(cfg);
113 |
114 | configuration_mutex_.lock();
115 |
116 | max_lin_acc_ = config.max_lin_acc;
117 | max_theta_acc_ = config.max_rot_acc;
118 | max_lin_vel_ = config.max_lin_vel;
119 | robot_.desiredVelocity = max_lin_vel_;
120 | min_lin_vel_ = config.min_lin_vel;
121 | max_vel_theta_ = config.max_rot_vel;
122 | min_vel_theta_ = config.min_rot_vel;
123 |
124 | sfm_goal_weight_ = config.sfm_goal_weight;
125 | sfm_obstacle_weight_ = config.sfm_obstacle_weight;
126 | sfm_people_weight_ = config.sfm_people_weight;
127 | robot_.params.forceFactorDesired = sfm_goal_weight_;
128 | robot_.params.forceFactorObstacle = sfm_obstacle_weight_;
129 | robot_.params.forceFactorSocial = sfm_people_weight_;
130 |
131 | // min_in_place_vel_th_ = config.min_in_place_rot_vel;
132 | // goal_lin_tolerance_ = config.xy_goal_tolerance;
133 | // goal_ang_tolerance_ = config.yaw_goal_tolerance;
134 | // wp_tolerance_ = config.wp_tolerance;
135 |
136 | sensor_iface_->setMaxLinVel(max_lin_vel_);
137 | collision_checker_->setVelParams(max_lin_vel_, max_vel_theta_, max_lin_acc_,
138 | max_theta_acc_);
139 |
140 | std::cout << std::endl
141 | << "SFM_CONTROLLER RECONFIGURED:" << std::endl
142 | << "max_lin_acc: " << max_lin_acc_ << std::endl
143 | << "max_rot_acc: " << max_theta_acc_ << std::endl
144 | << "min_lin_vel: " << min_lin_vel_ << std::endl
145 | << "max_lin_vel: " << max_lin_vel_ << std::endl
146 | << "min_rot_vel: " << min_vel_theta_ << std::endl
147 | << "max_rot_vel: " << max_vel_theta_ << std::endl
148 | << "sfm_goal_weight: " << sfm_goal_weight_ << std::endl
149 | << "sfm_obstacle_weight: " << sfm_obstacle_weight_ << std::endl
150 | << "sfm_social_weight: " << sfm_people_weight_ << std::endl
151 | << std::endl;
152 |
153 | configuration_mutex_.unlock();
154 | }
155 |
156 | /**
157 | * @brief method to update the scenario situation
158 | * @param path global path to be followed by the local controller
159 | * @return True when the goal has been reached, False otherwise
160 | */
161 | bool SFMController::update(std::vector path) {
162 |
163 | std::vector agents = sensor_iface_->getAgents();
164 |
165 | configuration_mutex_.lock();
166 | // update robot
167 | robot_.position = agents[0].position;
168 | robot_.yaw = agents[0].yaw;
169 | robot_.linearVelocity = agents[0].linearVelocity;
170 | robot_.angularVelocity = agents[0].angularVelocity;
171 | robot_.velocity = agents[0].velocity;
172 | robot_.obstacles1.clear();
173 | robot_.obstacles1 = agents[0].obstacles1;
174 |
175 | // Update the rest of agents
176 | agents_.clear();
177 | if (!agents.empty())
178 | agents_.assign(++agents.begin(), agents.end());
179 |
180 | // If we have to rotate, we do not look for a new goal
181 | if (rotate_) {
182 | sfm::Goal g;
183 | g.center.set(goal_.position.x, goal_.position.y);
184 | g.radius = goal_tolerance_;
185 | robot_.goals.clear();
186 | robot_.goals.push_back(g);
187 | if (goal_reached_) {
188 | goal_reached_ = false;
189 | rotate_ = false;
190 | configuration_mutex_.unlock();
191 | return true;
192 | }
193 | configuration_mutex_.unlock();
194 | return false;
195 | }
196 |
197 | // Update the robot goal
198 | geometry_msgs::PoseStamped min;
199 | float min_dist = 9999.0;
200 | bool goal_found = false;
201 | for (unsigned int i = path.size() - 1; i > 0; i--) {
202 | float dx = path[i].pose.position.x - robot_.position.getX();
203 | float dy = path[i].pose.position.y - robot_.position.getY();
204 | float d = sqrt(dx * dx + dy * dy);
205 | if (d < min_dist) {
206 | min_dist = d;
207 | min = path[i];
208 | }
209 | if (d <= local_goal_dist_) {
210 | if (d < goal_tolerance_) {
211 | configuration_mutex_.unlock();
212 | // goal reached! - rotate!
213 | printf("Update. Goal location reached. Rotating...\n");
214 | rotate_ = true;
215 | return false;
216 | }
217 | sfm::Goal g;
218 | goal_ = path[i].pose;
219 | g.center.set(path[i].pose.position.x, path[i].pose.position.y);
220 | g.radius = goal_tolerance_;
221 | robot_.goals.clear();
222 | robot_.goals.push_back(g);
223 | goal_found = true;
224 | goal_reached_ = false;
225 | // printf("Update. Goal found! x: %.2f, y: %.2f\n", g.center.getX(),
226 | // g.center.getY());
227 | publishSFMGoal(path[i]);
228 | configuration_mutex_.unlock();
229 | return false;
230 | }
231 | }
232 | // printf("Goal not found closer than %.2f,\n", local_goal_dist_);
233 | // printf("the closest is %.2f m\n", min_dist);
234 | if (!path.empty()) {
235 | sfm::Goal g;
236 | goal_ = min.pose;
237 | g.center.set(min.pose.position.x, min.pose.position.y);
238 | g.radius = goal_tolerance_;
239 | robot_.goals.clear();
240 | robot_.goals.push_back(g);
241 | goal_reached_ = false;
242 | publishSFMGoal(min);
243 | } else {
244 | printf("Update. Goal not found. Received path size: %i\n",
245 | (int)path.size());
246 | }
247 | configuration_mutex_.unlock();
248 | return false;
249 | }
250 |
251 | /**
252 | * @brief method to compute the velocity command of the robot
253 | * @param cmd_vel velocity message to be filled
254 | * @return True if a command vel was found
255 | */
256 | bool SFMController::computeAction(geometry_msgs::Twist &cmd_vel) {
257 |
258 | configuration_mutex_.lock();
259 | double dt = (ros::Time::now() - last_command_time_).toSec();
260 | // printf("dt: %.4f\n", dt);
261 | if (dt > 0.2)
262 | dt = 0.1;
263 |
264 | last_command_time_ = ros::Time::now();
265 |
266 | // We must rotate to reach the goal position
267 | if (rotate_) {
268 | float ang_diff = robot_.yaw.toRadian() - tf::getYaw(goal_.orientation);
269 | ang_diff = normalizeAngle(ang_diff, -M_PI, M_PI);
270 | if (fabs(ang_diff) < yaw_tolerance_) {
271 | printf("Angdiff (%.2f) < yaw_tolerance (%.2f)\n", fabs(ang_diff),
272 | yaw_tolerance_);
273 | cmd_vel.linear.x = 0.0;
274 | cmd_vel.linear.y = 0.0;
275 | cmd_vel.linear.z = 0.0;
276 | cmd_vel.angular.x = 0.0;
277 | cmd_vel.angular.y = 0.0;
278 | cmd_vel.angular.z = 0.0;
279 | goal_reached_ = true;
280 | configuration_mutex_.unlock();
281 | return true;
282 | } else if (ang_diff > 0.0) {
283 | cmd_vel.angular.z = -min_rot_in_place_;
284 | // printf("Rotating with vel: %.2f\n", cmd_vel.angular.z);
285 | } else {
286 | cmd_vel.angular.z = min_rot_in_place_;
287 | // printf("Rotating with vel: %.2f\n", cmd_vel.angular.z);
288 | }
289 | configuration_mutex_.unlock();
290 | return true;
291 | }
292 |
293 | // Compute Social Forces
294 | sfm::SFM.computeForces(robot_, agents_);
295 |
296 | // Compute velocity of the robot
297 | robot_.velocity += robot_.forces.globalForce * dt;
298 | if (robot_.velocity.norm() > robot_.desiredVelocity) {
299 | robot_.velocity.normalize();
300 | robot_.velocity *= robot_.desiredVelocity;
301 | }
302 |
303 | // The resultant total velocity is expressed in the odom frame. Transform
304 | // to robot_frame
305 | geometry_msgs::Vector3 velocity;
306 | velocity.x = robot_.velocity.getX();
307 | velocity.y = robot_.velocity.getY();
308 | geometry_msgs::Vector3 localV =
309 | sensor_iface_->transformVector(velocity, planner_frame_, robot_frame_);
310 |
311 | utils::Vector2d vel;
312 | vel.set(localV.x, localV.y);
313 | cmd_vel.linear.x = vel.norm();
314 |
315 | // Decrease speed to approach the goal
316 | float dx = goal_.position.x - robot_.position.getX();
317 | float dy = goal_.position.y - robot_.position.getY();
318 | float d = sqrt(dx * dx + dy * dy);
319 | if (d < 1.0)
320 | cmd_vel.linear.x =
321 | (vel.norm() * d) < min_lin_vel_ ? min_lin_vel_ : (vel.norm() * d);
322 |
323 | cmd_vel.linear.y = 0.0;
324 | cmd_vel.linear.z = 0.0;
325 | // Wz = std::atan2(localV.y,localV.x)/2.0;
326 | double angle = std::atan2(localV.y, localV.x);
327 | cmd_vel.angular.z = getVel(max_vel_theta_, a_, angle);
328 | cmd_vel.angular.x = 0.0;
329 | cmd_vel.angular.y = 0.0;
330 |
331 | // Prevent the robot for turning around:
332 | // If the angle difference between the desired force
333 | // and the global force is almost opossite,
334 | // stop the robot instead of turning around
335 | double angle_deg =
336 | robot_.forces.desiredForce.angleTo(robot_.forces.globalForce).toDegree();
337 | if ((180.0 - fabs(angle_deg)) < 25.0) {
338 | // printf("\nStopping robot. angle_deg: %.3f!!!!!\n", angle_deg);
339 | cmd_vel.linear.x = 0.0;
340 | cmd_vel.angular.z = 0.0;
341 | }
342 |
343 | publishForces();
344 | if (!collision_checker_->checkCommand(
345 | robot_.linearVelocity, 0.0, robot_.angularVelocity, cmd_vel.linear.x,
346 | 0.0, cmd_vel.angular.z, 0.11)) {
347 | ROS_WARN("Possible collision detected! Sending zero vel!");
348 | cmd_vel.linear.x = 0.0;
349 | cmd_vel.angular.z = 0.0;
350 | }
351 |
352 | // ROS_INFO("LV: %f; AV: %f", cmd_vel.linear.x, cmd_vel.angular.z);
353 | configuration_mutex_.unlock();
354 | return true;
355 | }
356 |
357 | /**
358 | * @brief check if the current scenario leads to a possible collision
359 | * @return True if a possible collision was detected, False otherwise
360 | */
361 | bool SFMController::fastCollisioncheck() {
362 | // return sensor_iface_->collisionCheck();
363 | return true;
364 | }
365 |
366 | /**
367 | * @brief Publish in RViz the local goal followed by the controller
368 | * @param g goal position
369 | * @return none
370 | */
371 | void SFMController::publishSFMGoal(const geometry_msgs::PoseStamped &g) {
372 | visualization_msgs::Marker marker;
373 | marker.header.frame_id = g.header.frame_id;
374 | marker.header.stamp = ros::Time::now();
375 | marker.ns = "sfm_goal";
376 | marker.id = 1;
377 | marker.action = visualization_msgs::Marker::ADD;
378 | marker.type = visualization_msgs::Marker::ARROW;
379 | marker.color = getColor(0.0, 0.0, 1.0, 1.0);
380 | marker.lifetime = ros::Duration(0.15);
381 | marker.scale.x = 0.4;
382 | marker.scale.y = 0.1;
383 | marker.scale.z = 0.1;
384 | marker.pose = g.pose;
385 | sfm_goal_pub_.publish(marker);
386 | }
387 |
388 | /**
389 | * @brief Publish an arrow marker in Rviz representing a force
390 | * @param index id of the marker
391 | * @param color RGB color of the marker
392 | * @param force force to be represented
393 | * @param markers markerArray in which the arrow will be added
394 | * @return none
395 | */
396 | void SFMController::publishForceMarker(
397 | unsigned index, const std_msgs::ColorRGBA &color,
398 | const utils::Vector2d &force, visualization_msgs::MarkerArray &markers) {
399 | visualization_msgs::Marker marker;
400 | marker.header.frame_id = planner_frame_;
401 | marker.header.stamp = ros::Time::now();
402 | marker.ns = "robot_forces";
403 | marker.id = index;
404 | marker.action = force.norm() > 1e-4 ? 0 : 2;
405 | marker.color = color;
406 | marker.lifetime = ros::Duration(1.0);
407 | marker.scale.x = std::max(1e-4, force.norm());
408 | marker.scale.y = 0.1;
409 | marker.scale.z = 0.1;
410 | marker.pose.position.x = robot_.position.getX();
411 | marker.pose.position.y = robot_.position.getY();
412 | marker.pose.position.z = 0;
413 | marker.pose.orientation =
414 | tf::createQuaternionMsgFromRollPitchYaw(0, 0, force.angle().toRadian());
415 | markers.markers.push_back(marker);
416 | }
417 |
418 | /**
419 | * @brief fill a ColorRGBA message
420 | * @param r value of the red componet [0-1]
421 | * @param g value of the green component [0-1]
422 | * @param b value of the blue component [0-1]
423 | * @param a transparency of the color [0-1]
424 | * @return a ROS ColorRGBA message
425 | */
426 | std_msgs::ColorRGBA SFMController::getColor(double r, double g, double b,
427 | double a) {
428 | std_msgs::ColorRGBA color;
429 | color.r = r;
430 | color.g = g;
431 | color.b = b;
432 | color.a = a;
433 | return color;
434 | }
435 |
436 | /**
437 | * @brief Publish the set of SFM forces in RViz
438 | * @return none
439 | */
440 | void SFMController::publishForces() {
441 | visualization_msgs::MarkerArray markers;
442 | publishForceMarker(0, getColor(1, 0, 0, 1), robot_.forces.obstacleForce,
443 | markers);
444 | publishForceMarker(1, getColor(0, 0, 1, 1), robot_.forces.socialForce,
445 | markers);
446 | // publishForceMarker(2, getColor(0, 1, 1, 1), robot_.forces.groupForce,
447 | // markers);
448 | publishForceMarker(3, getColor(0, 1, 0, 1), robot_.forces.desiredForce,
449 | markers);
450 | publishForceMarker(4, getColor(1, 1, 1, 1), robot_.forces.globalForce,
451 | markers);
452 | publishForceMarker(5, getColor(1, 1, 0, 1), robot_.velocity, markers);
453 | robot_markers_pub_.publish(markers);
454 | // ROS_INFO_STREAM("Goal: " << robot_.forces.desiredForce.norm()
455 | // << ", Obstacle: "
456 | // << robot_.forces.obstacleForce.norm() << ", People:
457 | // "
458 | // << robot_.forces.socialForce.norm() << std::endl);
459 | }
460 |
461 | } // namespace sfm_controller
462 | } // namespace Navigation
463 | } // namespace Upo
--------------------------------------------------------------------------------
/src/sensor_interface.cpp:
--------------------------------------------------------------------------------
1 | /**
2 | * Class to adapt the sensory input and detections to be used by the
3 | * sfm_local_controller
4 | *
5 | * Author: Noé Pérez-Higueras
6 | * Service Robotics Lab, Pablo de Olavide University 2021
7 | *
8 | * Software License Agreement (BSD License)
9 | *
10 | */
11 |
12 | #include
13 |
14 | namespace Upo {
15 | namespace Navigation {
16 | namespace sfm_controller {
17 |
18 | /**
19 | * @brief Default constructor
20 | * @param n pointer to a ros node handle to publish to topics
21 | * @param tfBuffer Pointer to tf2 buffer
22 | * @param robot_max_lin_speed the maximum robot linear speed [m/s]
23 | * @param robot_radius the radius of the robot circunference [m]
24 | * @param person_radius the approximated radius of the people body
25 | * @param robot_frame the coordinate frame of the robot base
26 | * @param odom_frame the coordinate frame of the robot odometry
27 | **/
28 | SFMSensorInterface::SFMSensorInterface(ros::NodeHandle *n,
29 | tf2_ros::Buffer *tfBuffer,
30 | float robot_max_lin_speed,
31 | float robot_radius, float person_radius,
32 | std::string robot_frame,
33 | std::string odom_frame)
34 | : nh_(n), n_(), tf_buffer_(tfBuffer), person_radius_(person_radius),
35 | robot_frame_(robot_frame), odom_frame_(odom_frame) {
36 |
37 | nh_->param("max_obstacle_dist", max_obstacle_dist_, float(3.0));
38 | nh_->param("naive_goal_time", naive_goal_time_, float(2.0));
39 | nh_->param("people_velocity", people_velocity_, float(1.0));
40 |
41 | laser_received_ = false;
42 | last_laser_ = ros::Time::now();
43 |
44 | std::string laser_topic;
45 | nh_->param("laser_topic", laser_topic, std::string("scan"));
46 | std::string people_topic;
47 | nh_->param("people_topic", people_topic, std::string("people"));
48 | std::string obs_topic;
49 | nh_->param("dyn_obs_topic", obs_topic, std::string("obstacles"));
50 | std::string odom_topic;
51 | nh_->param("odom_topic", odom_topic, std::string("odom"));
52 |
53 | std::cout << std::endl
54 | << "SFM SENSOR INTERFACE:" << std::endl
55 | << "laser_topic: " << laser_topic << std::endl
56 | << "people_topic: " << people_topic << std::endl
57 | << "dyn_obs_topic: " << obs_topic << std::endl
58 | << "odom_topic: " << odom_topic << std::endl
59 | << "max_obstacle_dist: " << max_obstacle_dist_ << std::endl
60 | << "naive_goal_time: " << naive_goal_time_ << std::endl
61 | << "people_velocity: " << people_velocity_ << std::endl
62 | << std::endl;
63 |
64 | // Initialize SFM. Just one agent (the robot)
65 | agents_.resize(1);
66 | agents_[0].desiredVelocity = robot_max_lin_speed;
67 | agents_[0].radius = robot_radius;
68 | agents_[0].cyclicGoals = false;
69 | agents_[0].teleoperated = true;
70 | agents_[0].groupId = -1;
71 |
72 | // ros::NodeHandle n;
73 | laser_sub_ = n_.subscribe(
74 | laser_topic.c_str(), 1, &SFMSensorInterface::laserCb, this);
75 |
76 | people_sub_ = n_.subscribe(
77 | people_topic.c_str(), 1, &SFMSensorInterface::peopleCb, this);
78 |
79 | dyn_obs_sub_ = n_.subscribe(
80 | obs_topic.c_str(), 1, &SFMSensorInterface::dynamicObsCb, this);
81 |
82 | odom_sub_ = n_.subscribe(
83 | odom_topic.c_str(), 1, &SFMSensorInterface::odomCb, this);
84 |
85 | points_pub_ = n_.advertise(
86 | "/sfm/markers/obstacle_points", 0);
87 |
88 | // sonars ?
89 | // sonar_sub_= n_.subscribe(
90 | // rt.c_str(), 1, boost::bind(&SFMSensorInterface::sonarCb, this, _1));
91 | }
92 |
93 | /**
94 | * @brief Destructor class
95 | */
96 | SFMSensorInterface::~SFMSensorInterface() {}
97 |
98 | /**
99 | * @brief Callback to process the laser scan sensory input.
100 | * @param laser laserScan message to be processed
101 | */
102 | void SFMSensorInterface::laserCb(
103 | const sensor_msgs::LaserScan::ConstPtr &laser) {
104 |
105 | laser_received_ = true;
106 | last_laser_ = ros::Time::now();
107 |
108 | ROS_INFO_ONCE("laser received");
109 |
110 | std::vector points;
111 | float angle = laser->angle_min;
112 |
113 | for (unsigned int i = 0; i < laser->ranges.size(); i++) {
114 |
115 | if (laser->ranges[i] < max_obstacle_dist_) {
116 |
117 | utils::Vector2d point(laser->ranges[i] * cos(angle),
118 | laser->ranges[i] * sin(angle));
119 | points.push_back(point);
120 | }
121 | angle += laser->angle_increment;
122 | // alpha += angle_inc;
123 | }
124 |
125 | if (points.empty()) {
126 | obstacles_ = points;
127 | return;
128 | }
129 |
130 | // Transform points to
131 | // odom_frame_ if necessary
132 | if (laser->header.frame_id != odom_frame_) {
133 | geometry_msgs::PointStamped out;
134 | for (unsigned int i = 0; i < points.size(); i++) {
135 | geometry_msgs::PointStamped in;
136 | in.header.frame_id = laser->header.frame_id;
137 | in.header.stamp = ros::Time(0);
138 | in.point.x = points[i].getX();
139 | in.point.y = points[i].getY();
140 | in.point.z = 0.0;
141 | try {
142 | geometry_msgs::PointStamped out =
143 | tf_buffer_->transform(in, odom_frame_);
144 | points[i].setX(out.point.x);
145 | points[i].setY(out.point.y);
146 |
147 | } catch (tf2::TransformException &ex) {
148 | ROS_WARN("Could NOT transform "
149 | "laser point to %s: "
150 | "%s",
151 | robot_frame_.c_str(), ex.what());
152 | return;
153 | }
154 | }
155 | }
156 |
157 | // Now check if the points
158 | // belong to a person or an
159 | // dynamic obstacle
160 |
161 | // transform people positions to
162 | // robot frame
163 | // people_mutex_.lock();
164 | people_msgs::People people = people_;
165 | // people_mutex_.unlock();
166 |
167 | std::vector people_points;
168 | if (people.header.frame_id != odom_frame_) {
169 | geometry_msgs::PointStamped person_point;
170 | person_point.header = people.header;
171 |
172 | for (auto person : people.people) {
173 | person_point.point = person.position;
174 | person_point.header.stamp = ros::Time(0);
175 | try {
176 | geometry_msgs::PointStamped p_point =
177 | tf_buffer_->transform(person_point, odom_frame_);
178 | people_points.push_back(p_point.point);
179 | } catch (tf2::TransformException &ex) {
180 | ROS_WARN("Could NOT transform "
181 | "person point to %s: "
182 | "%s",
183 | odom_frame_.c_str(), ex.what());
184 | return;
185 | }
186 | }
187 | } else {
188 | for (auto person : people.people) {
189 | people_points.push_back(person.position);
190 | }
191 | }
192 | // Remove the points in the
193 | // people radius
194 | std::vector points_aux;
195 | for (utils::Vector2d p : points) {
196 | bool remove = false;
197 | for (auto person : people_points) {
198 | float dx = p.getX() - person.x;
199 | float dy = p.getY() - person.y;
200 | float d = std::hypotf(dx, dy);
201 | if (d <= person_radius_) {
202 | remove = true;
203 | break;
204 | }
205 | }
206 | if (!remove)
207 | points_aux.push_back(p);
208 | }
209 | points.clear();
210 | points = points_aux;
211 | // we can have one point per
212 | // sector as much
213 | if (points.empty()) {
214 | obstacles_ = points;
215 | return;
216 | }
217 |
218 | // transform dynamic obstacles
219 | // positions to robot frame
220 | // obs_mutex_.lock();
221 | dynamic_obstacle_detector::DynamicObstacles obstacles = dyn_obs_;
222 | // obs_mutex_.unlock();
223 |
224 | std::vector ob_points;
225 | if (obstacles.header.frame_id != odom_frame_) {
226 | geometry_msgs::PointStamped ob_point;
227 | ob_point.header = obstacles.header;
228 | // ob_point.stamp = ros::Time();
229 | for (auto obstacle : obstacles.obstacles) {
230 | ob_point.point = obstacle.position;
231 | ob_point.header.stamp = ros::Time(0);
232 | try {
233 | geometry_msgs::PointStamped o_point =
234 | tf_buffer_->transform(ob_point, odom_frame_);
235 | ob_points.push_back(o_point.point);
236 | } catch (tf2::TransformException &ex) {
237 | ROS_WARN("Could NOT transform "
238 | "obstacle point to %s: "
239 | "%s",
240 | odom_frame_.c_str(), ex.what());
241 | return;
242 | }
243 | }
244 | }
245 | // Remove the points in the
246 | // object radius (approximated
247 | // by person radius because we
248 | // do not know the radius)
249 | points_aux.clear();
250 | for (utils::Vector2d p : points) {
251 | bool remove = false;
252 | for (auto ob : ob_points) {
253 | float dx = p.getX() - ob.x;
254 | float dy = p.getY() - ob.y;
255 | float d = std::hypotf(dx, dy);
256 | if (d <= person_radius_) {
257 | remove = true;
258 | break;
259 | }
260 | }
261 | if (!remove)
262 | points_aux.push_back(p);
263 | }
264 | points.clear();
265 | points = points_aux;
266 |
267 | obstacles_ = points;
268 | publish_obstacle_points(points);
269 | }
270 |
271 | /**
272 | * @brief publish the transformed points in RViz
273 | * @param points vector with the coordinates of the points
274 | * @return none
275 | */
276 | void SFMSensorInterface::publish_obstacle_points(
277 | const std::vector &points) {
278 |
279 | visualization_msgs::Marker m;
280 | m.header.frame_id = odom_frame_; // robot_frame_
281 | m.header.stamp = ros::Time::now();
282 | m.ns = "sfm_obstacle_points";
283 | m.type = visualization_msgs::Marker::POINTS;
284 | m.action = visualization_msgs::Marker::ADD;
285 | m.pose.orientation.x = 0.0;
286 | m.pose.orientation.y = 0.0;
287 | m.pose.orientation.z = 0.0;
288 | m.pose.orientation.w = 1.0;
289 | m.scale.x = 0.15;
290 | m.scale.y = 0.15;
291 | m.scale.z = 0.15;
292 | m.color.r = 1.0;
293 | m.color.g = 0.0;
294 | m.color.b = 0.0;
295 | m.color.a = 1.0;
296 | m.id = 1000;
297 | m.lifetime = ros::Duration(0.3);
298 | // printf("Published Obstacles: ");
299 | for (utils::Vector2d p : points) {
300 | geometry_msgs::Point pt;
301 | pt.x = p.getX();
302 | pt.y = p.getY();
303 | // printf("x: %.2f, y: %.2f -", pt.x, pt.y);
304 | pt.z = 0.2;
305 | m.points.push_back(pt);
306 | }
307 | // printf("\n");
308 | points_pub_.publish(m);
309 | }
310 |
311 | /**
312 | * @brief Callback to process the moving obstacles detected in the robot
313 | * vecinity.
314 | * @param obs messages with the obstacles to be processed.
315 | */
316 | void SFMSensorInterface::dynamicObsCb(
317 | const dynamic_obstacle_detector::DynamicObstacles::ConstPtr &obs) {
318 | ROS_INFO_ONCE("Dyamic obs received");
319 |
320 | // obs_mutex_.lock();
321 | dyn_obs_ = *obs;
322 | // obs_mutex_.unlock();
323 |
324 | std::vector agents;
325 |
326 | // check if people are not in odom frame
327 | geometry_msgs::PointStamped ps;
328 | for (unsigned i = 0; i < obs->obstacles.size(); i++) {
329 | sfm::Agent ag;
330 | ps.header.frame_id = obs->header.frame_id;
331 | ps.header.stamp = ros::Time(0);
332 | ps.point = obs->obstacles[i].position;
333 | if (obs->header.frame_id != odom_frame_) {
334 | geometry_msgs::PointStamped p;
335 | try {
336 | p = tf_buffer_->transform(ps, odom_frame_);
337 | ps = p;
338 | } catch (tf2::TransformException &ex) {
339 | ROS_WARN("No transform %s", ex.what());
340 | }
341 | }
342 | ag.position.set(ps.point.x, ps.point.y);
343 |
344 | geometry_msgs::Vector3 velocity;
345 | velocity.x = obs->obstacles[i].velocity.x;
346 | velocity.y = obs->obstacles[i].velocity.y;
347 |
348 | geometry_msgs::Vector3 localV = SFMSensorInterface::transformVector(
349 | velocity, obs->header.frame_id, odom_frame_);
350 |
351 | ag.yaw = utils::Angle::fromRadian(atan2(localV.y, localV.x));
352 | ag.velocity.set(localV.x, localV.y);
353 | ag.linearVelocity = ag.velocity.norm();
354 | ag.radius = person_radius_;
355 | ag.teleoperated = false;
356 | // if (fabs(people->people[i].vel) < 0.05) {
357 | // agents[i+1].velocity.set(0,0);
358 | //}
359 |
360 | // The SFM requires a local goal for each agent. We will assume that the
361 | // goal for people depends on its current velocity
362 | ag.goals.clear();
363 | sfm::Goal naiveGoal;
364 | // No group consideration for the moment
365 | utils::Vector2d v = ag.position + naive_goal_time_ * ag.velocity;
366 | naiveGoal.center.set(v.getX(), v.getY());
367 | naiveGoal.radius = person_radius_;
368 | ag.goals.push_back(naiveGoal);
369 | ag.desiredVelocity = people_velocity_;
370 | ag.groupId = -1;
371 | agents.push_back(ag);
372 | }
373 |
374 | // Fill the obstacles of the agents
375 | std::vector obs_points = obstacles_;
376 | for (unsigned int i = 0; i < agents.size(); i++) {
377 | agents[i].obstacles1.clear();
378 | agents[i].obstacles1 = obs_points;
379 | }
380 |
381 | agents_mutex_.lock();
382 | agents_.resize(obs->obstacles.size() + 1);
383 | agents_[0].obstacles1 = obs_points;
384 | for (unsigned int i = 1; i < agents_.size(); i++)
385 | agents_[i] = agents[i - 1];
386 | agents_mutex_.unlock();
387 | }
388 |
389 | /**
390 | * @brief Callback to process the people detected in the robot vecinity.
391 | * @param people message with the people to be processed
392 | */
393 | void SFMSensorInterface::peopleCb(const people_msgs::People::ConstPtr &people) {
394 | ROS_INFO_ONCE("People received");
395 |
396 | // people_mutex_.lock();
397 | people_ = *people;
398 | // people_mutex_.unlock();
399 |
400 | std::vector agents;
401 |
402 | // check if people are not in odom frame
403 | geometry_msgs::PointStamped ps;
404 | for (unsigned i = 0; i < people->people.size(); i++) {
405 | sfm::Agent ag;
406 | ps.header.frame_id = people->header.frame_id;
407 | ps.header.stamp = ros::Time(0);
408 | ps.point = people->people[i].position;
409 | if (people->header.frame_id != odom_frame_) {
410 | geometry_msgs::PointStamped p;
411 | try {
412 | p = tf_buffer_->transform(ps, odom_frame_);
413 | ps = p;
414 | } catch (tf2::TransformException &ex) {
415 | ROS_WARN("No transform %s", ex.what());
416 | }
417 | }
418 | ag.position.set(ps.point.x, ps.point.y);
419 |
420 | geometry_msgs::Vector3 velocity;
421 | velocity.x = people->people[i].velocity.x;
422 | velocity.y = people->people[i].velocity.y;
423 |
424 | geometry_msgs::Vector3 localV = SFMSensorInterface::transformVector(
425 | velocity, people->header.frame_id, odom_frame_);
426 |
427 | ag.yaw = utils::Angle::fromRadian(atan2(localV.y, localV.x));
428 | ag.velocity.set(localV.x, localV.y);
429 | ag.linearVelocity = agents_[i + 1].velocity.norm();
430 | ag.radius = person_radius_;
431 | ag.teleoperated = false;
432 | // if (fabs(people->people[i].vel) < 0.05) {
433 | // agents[i+1].velocity.set(0,0);
434 | //}
435 |
436 | // The SFM requires a local goal for each agent. We will assume that the
437 | // goal for people depends on its current velocity
438 | ag.goals.clear();
439 | sfm::Goal naiveGoal;
440 | // No group consideration for the moment
441 | // naiveGoal.center =
442 | // agents_[i + 1].position + naive_goal_time_ * agents_[i + 1].velocity;
443 | utils::Vector2d v = ag.position + naive_goal_time_ * ag.velocity;
444 | naiveGoal.center.set(v.getX(), v.getY());
445 | naiveGoal.radius = person_radius_;
446 | ag.goals.push_back(naiveGoal);
447 | ag.desiredVelocity = people_velocity_;
448 | ag.groupId = -1;
449 | agents.push_back(ag);
450 | }
451 |
452 | // Fill the obstacles of the agents
453 | std::vector obs_points = obstacles_;
454 | for (unsigned int i = 0; i < agents.size(); i++) {
455 | agents[i].obstacles1.clear();
456 | agents[i].obstacles1 = obs_points;
457 | }
458 |
459 | agents_mutex_.lock();
460 | agents_.resize(people->people.size() + 1);
461 | agents_[0].obstacles1 = obs_points;
462 | for (unsigned int i = 1; i < agents_.size(); i++)
463 | agents_[i] = agents[i - 1];
464 | agents_mutex_.unlock();
465 | }
466 |
467 | /**
468 | * @brief Callback to process the odometry messages with the robot movement.
469 | * @param odom messages with the obstacles to be processed.
470 | */
471 | void SFMSensorInterface::odomCb(const nav_msgs::Odometry::ConstPtr &odom) {
472 | ROS_INFO_ONCE("Odom received");
473 |
474 | // if (odom->header.frame_id != odom_frame_) {
475 | // ROS_INFO("Odometry frame is %s, it should be %s",
476 | // odom->header.frame_id.c_str(), odom_frame_.c_str());
477 | // // return;
478 | // }
479 |
480 | agents_mutex_.lock();
481 | sfm::Agent agent = agents_[0];
482 | agents_mutex_.unlock();
483 |
484 | agent.position.set(odom->pose.pose.position.x, odom->pose.pose.position.y);
485 | agent.yaw = utils::Angle::fromRadian(tf::getYaw(odom->pose.pose.orientation));
486 |
487 | agent.linearVelocity =
488 | std::sqrt(odom->twist.twist.linear.x * odom->twist.twist.linear.x +
489 | odom->twist.twist.linear.y * odom->twist.twist.linear.y);
490 | agent.angularVelocity = odom->twist.twist.angular.z;
491 |
492 | // The velocity in the odom messages is in the robot local frame!!!
493 | geometry_msgs::Vector3 velocity;
494 | velocity.x = odom->twist.twist.linear.x;
495 | velocity.y = odom->twist.twist.linear.y;
496 |
497 | geometry_msgs::Vector3 localV =
498 | SFMSensorInterface::transformVector(velocity, robot_frame_, odom_frame_);
499 | agent.velocity.set(localV.x, localV.y);
500 |
501 | // Update agent[0] (the robot) with odom.
502 | agents_mutex_.lock();
503 | agents_[0] = agent;
504 | agents_mutex_.unlock();
505 | }
506 |
507 | // void SFMSensorInterface::updateAgents() {
508 | // Get odom
509 | //}
510 |
511 | /**
512 | * @brief returns the vector of sfm agents
513 | * @return agents vector
514 | */
515 | std::vector SFMSensorInterface::getAgents() {
516 | agents_mutex_.lock();
517 | std::vector agents = agents_;
518 | agents_mutex_.unlock();
519 | return agents;
520 | }
521 |
522 | /**
523 | * @brief Tranform a coordinate vector from one frame to another
524 | * @param vector coordinate vector in the origin frame
525 | * @param from string with the name of the origin frame
526 | * @param to string with the name of the target frame
527 | * @return coordinate vector in the target frame
528 | */
529 | geometry_msgs::Vector3
530 | SFMSensorInterface::transformVector(geometry_msgs::Vector3 &vector,
531 | std::string from, std::string to) {
532 | geometry_msgs::Vector3 nextVector;
533 |
534 | geometry_msgs::Vector3Stamped v;
535 | geometry_msgs::Vector3Stamped nv;
536 |
537 | v.header.frame_id = from;
538 |
539 | // we'll just use the most recent transform available for our simple example
540 | v.header.stamp = ros::Time(0);
541 |
542 | // just an arbitrary point in space
543 | v.vector.x = vector.x;
544 | v.vector.y = vector.y;
545 | v.vector.z = 0.0;
546 |
547 | try {
548 | nv = tf_buffer_->transform(v, to);
549 | } catch (tf2::TransformException &ex) {
550 | ROS_WARN("No transform %s", ex.what());
551 | }
552 |
553 | nextVector.x = nv.vector.x;
554 | nextVector.y = nv.vector.y;
555 | nextVector.z = 0.0;
556 |
557 | return nextVector;
558 | }
559 |
560 | } // namespace sfm_controller
561 | } // namespace Navigation
562 | } // namespace Upo
--------------------------------------------------------------------------------