Check for collisions in motion primitives#889
Check for collisions in motion primitives#889bpwilcox merged 20 commits intoros-navigation:masterfrom
Conversation
53d6c02 to
8ab347d
Compare
|
Changing the robot radius default to match the turtlebot3 documentation significantly improved the performance both during normal operation and using motion primitives. The footprint is centered off the base link, which is seemingly the between the wheels on the front, so it is not symmetrical around the robot's center. We may want to define the actual pointwise footprint later. |
One alternative would be to define a static transform between the base link and the robot center. Seems like the costmaps should reference the center of the robot rather than the location of the drive system. We can open an issue and address this later. |
|
|
||
| ~CollisionChecker(); | ||
|
|
||
| double scorePose( |
There was a problem hiding this comment.
Why the line break for these three methods?
| { | ||
| costmap_pub_->on_activate(); | ||
| costmap_update_pub_->on_activate(); | ||
| costmap_raw_pub_->on_activate(); |
There was a problem hiding this comment.
Reading the code, I'm not clear yet on what costmap_raw_pub_ is. A comment would be helpful. I suppose there is some kind of raw costmap that is being published?
There was a problem hiding this comment.
I wanted to change the name of all the pubs actually. The costmap_raw_pub publishes the costmap using the native values from the costmap2D array. The original costmap_pub' is publishing a translated occupancy grid version for rviz visualization. Would it perhaps be more helpful to change the costmap_pubtocostmap_rviz_pub`?
There was a problem hiding this comment.
No, I wouldn't specify who I expect to be listening. Just keep it to a topic name that anyone can listen to.
| nav2_util::LifecycleNode::SharedPtr node, | ||
| std::string & topic_name); | ||
|
|
||
| CostmapSubscriber( |
There was a problem hiding this comment.
Nice. Glad you included this version of the constructor. I'm thinking we should standardize on provided all three kinds of constructors: rclcpp::Node, rclcpp_lifecycle::LifecycleNode, and the interfaces version. The first two end up just using the third and are provided for convenience.
There was a problem hiding this comment.
Right, I'll add the rclcpp::Node version. Also, in the class I'm only currently using say one of the node interfaces, should we always stick to pattern even if we never use the other interfaces?
There was a problem hiding this comment.
If we expect it to be a class that might have multiple clients, then I would include them. If it's done specifically for just one module implementation, you can probably skip it.
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| #include "nav2_costmap_2d/collision_checker.hpp" |
There was a problem hiding this comment.
FYI - The convention I've been using for implementation (.cpp) files:
Include the file's header first. In this case collision_checker.hpp. Next, another section to include any standard header files. Finally, the last section includes any headers that are required by the implementation. Each of the three sections are sorted.
So, in this case, it would look like:
#include "nav2_costmap_2d/collision_checker.hpp"
#include "nav2_costmap_2d/cost_values.hpp"
#include "nav2_costmap_2d/exceptions.hpp"
#include "nav2_costmap_2d/footprint.hpp"
#include "nav2_util/line_iterator.hpp"There was a problem hiding this comment.
I use that convention too, this was an oversight, thanks for catching!
| collision_checker_ = std::make_unique<nav2_costmap_2d::CollisionChecker>( | ||
| costmap_sub_, footprint_sub_, get_name()); | ||
|
|
||
| get_robot_pose_service_ = rclcpp_node_->create_service<nav2_msgs::srv::GetRobotPose>( |
There was a problem hiding this comment.
I think you should be able to create the service off of the main lifecycle node and therefore not require the rclcpp_node.
There was a problem hiding this comment.
I had tried to do that before, but it did not work since the service was hanging because I'm not actually spinning the lifecycle node here in this test. I used the rclcpp_node because it is spinning in a thread.
| nav2_msgs::msg::Costmap | ||
| toCostmapMsg(nav2_costmap_2d::Costmap2D * costmap) | ||
| { | ||
| double resolution = costmap->getResolution(); |
There was a problem hiding this comment.
A personal, stylistic thing, but I'd organize this code to get the various values first, and then populate the structure. For example,
double resolution = costmap->getResolution();
double wx, wy;
costmap->mapToWorld(0, 0, wx, wy);
unsigned char * data = costmap->getCharMap();
nav2_msgs::msg::Costmap costmap_msg;
costmap_msg.header.frame_id = global_frame_;
costmap_msg.header.stamp = now();
costmap_msg.metadata.layer = "master";
costmap_msg.metadata.resolution = resolution;
costmap_msg.metadata.size_x = costmap->getSizeInCellsX();
costmap_msg.metadata.size_y = costmap->getSizeInCellsY();
costmap_msg.metadata.origin.position.x = wx - resolution / 2;
costmap_msg.metadata.origin.position.y = wy - resolution / 2;
costmap_msg.metadata.origin.position.z = 0.0;
costmap_msg.metadata.origin.orientation.w = 1.0;
costmap_msg.data.resize(costmap_msg.metadata.size_x * costmap_msg.metadata.size_y);
for (unsigned int i = 0; i < costmap_msg.data.size(); i++) {
costmap_msg.data[i] = data[i];
}
return costmap_msg;I think the result is a bit easier to read and to see all of the structure fields.
| if (!collision_checker_->isCollisionFree(pose2d)) { | ||
| stopRobot(); | ||
| RCLCPP_INFO(node_->get_logger(), "Collision Ahead -Exiting Spin "); | ||
| return Status::SUCCEEDED; |
There was a problem hiding this comment.
Should is case be considered successful? We weren't able to fully execute the spin.
There was a problem hiding this comment.
I thought about this, so I welcome feedback. If we return failure, then I noticed that the recovery behaviors exit and the system returns navigation failed. If we have another recovery behavior queued after spin, then we'd want to attempt to execute it even if we could not fully execute the spin. Also, I don't think that being unable to execute the full spin because of an impending collision is considered a "failure". If we can only turn 90 degrees out of the full 180 because a block prevents the rest, I'd still consider that recovery a success so far as it was implemented without any system failures or missing any data dependencies. If we DID consider it a failure, then I would still want that we don't break out of the recovery tree.
There was a problem hiding this comment.
I think we should return FAILED, the action server will set the goal as aborted in that case.
There was a problem hiding this comment.
I don't think we should abort the goal. Even if we consider this one recovery behavior "FAILED", I don't think we should necessarily abort the goal, since the next recovery could be successful. But firstly, I would want to hear why you think we should consider it a failure if the spin doesn't complete because of an impending obstacle. To me, that's sensible behavior that you would spin as much as you can before finishing and moving onto next recovery if needed, not that the spin itself failed.
There was a problem hiding this comment.
Ok with me to return FAILED for now, but we should address this ASAP.
| #include "geometry_msgs/msg/pose_stamped.hpp" | ||
| #include "geometry_msgs/msg/pose2_d.hpp" | ||
| #include "nav2_costmap_2d/costmap_2d.hpp" | ||
| #include "nav2_costmap_2d/costmap_subscriber.hpp" |
There was a problem hiding this comment.
Minor recommendation, some of these includes could be replaced with forward declarations:
class CostmapSubscriber;
class FootprintSubscriber;
class Costmap2D;| const geometry_msgs::msg::Pose2D & pose); | ||
| bool isCollisionFree( | ||
| const geometry_msgs::msg::Pose2D & pose); | ||
| bool getRobotPose( |
There was a problem hiding this comment.
Not sure if getRobotPose() is something we want from the CollisionChecker, maybe better to make it private.
There was a problem hiding this comment.
I made it public so that I can call it in the motion primitives as opposed to creating a new service and function for the motion primitives. My assumption was that since the motion primitives base class creates a collision_checker, it would be redundant to duplicate this functionality. What do you think?
|
|
||
| std::string name_; | ||
| nav2_util::GetRobotPoseClient get_robot_pose_client_{"collision_checker"}; | ||
| std::shared_ptr<CostmapSubscriber> costmap_sub_; |
There was a problem hiding this comment.
Consider references instead of pointers.
| } | ||
|
|
||
| bool | ||
| CollisionChecker::getRobotPose(geometry_msgs::msg::Pose & current_pose) |
| auto request = std::make_shared<nav2_util::GetRobotPoseClient::GetRobotPoseRequest>(); | ||
|
|
||
| auto result = get_robot_pose_client_.invoke(request, 1s); | ||
| if (!result.get()->is_pose_valid) { |
There was a problem hiding this comment.
!result->is_pose_valid should also work.
| if (!result.get()->is_pose_valid) { | ||
| return false; | ||
| } | ||
| current_pose = result.get()->pose.pose; |
There was a problem hiding this comment.
No need to get the pointed object.
| return line_cost; | ||
| } | ||
|
|
||
| double CollisionChecker::pointCost(int x, int y) |
| return footprint_cost; | ||
| } | ||
|
|
||
| double CollisionChecker::lineCost(int x0, int x1, int y0, int y1) |
|
|
||
| // now we really have to lay down the footprint in the costmap_ grid | ||
| unsigned int x0, x1, y0, y1; | ||
| double line_cost = 0.0; |
There was a problem hiding this comment.
line_cost could be declared inside the for loop
There was a problem hiding this comment.
Why would we want to do that here?
adding test cases move collision checker to nav2_costmap_2d
revert dwb_critics cmake changes
orduno
left a comment
There was a problem hiding this comment.
Looks very good. Just some minor recommendations.
| } | ||
|
|
||
| line_cost = lineCost(x0, x1, y0, y1); | ||
| footprint_cost = std::max(line_cost, footprint_cost); |
There was a problem hiding this comment.
max_cost = std::max(lineCost(...), max_cost);
| // we need to rasterize each line in the footprint | ||
| for (unsigned int i = 0; i < footprint.size() - 1; ++i) { | ||
| // get the cell coord of the first point | ||
| if (!costmap_->worldToMap(footprint[i].x, footprint[i].y, x0, y0)) { |
| // TODO(bpwilcox): port onNewSubscription functionality for publisher | ||
| costmap_pub_ = ros_node->create_publisher<nav_msgs::msg::OccupancyGrid>(topic_name, | ||
| custom_qos); | ||
| costmap_raw_pub_ = ros_node->create_publisher<nav2_msgs::msg::Costmap>(topic_name + "_raw", |
| rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_; | ||
|
|
||
| void toCostmap2D(); | ||
| void costmap_callback(const nav2_msgs::msg::Costmap::SharedPtr msg); |
| public: | ||
| explicit CollisionCheckerException(const std::string description) | ||
| : std::runtime_error(description) {} | ||
| typedef std::shared_ptr<CollisionCheckerException> Ptr; |
|
|
||
| if (!robot_->getOdometry(initial_pose_)) { | ||
| RCLCPP_ERROR(node_->get_logger(), "initial robot odom pose is not available."); | ||
| if (!collision_checker_->getRobotPose(initial_pose_)) { |
There was a problem hiding this comment.
Alternatively use the GetRobotPoseClient.
There was a problem hiding this comment.
So we duplicate the GetRobotPoseClient here and in the collision_checker?
There was a problem hiding this comment.
Based on our conversation, can we reference an issue # so we can track it later?
|
|
||
| if (!collision_checker_->isCollisionFree(pose2d)) { | ||
| stopRobot(); | ||
| RCLCPP_INFO(node_->get_logger(), "Collision Ahead - Exiting BackUp"); |
| if (!collision_checker_->isCollisionFree(pose2d)) { | ||
| stopRobot(); | ||
| RCLCPP_INFO(node_->get_logger(), "Collision Ahead - Exiting BackUp"); | ||
| return Status::SUCCEEDED; |
There was a problem hiding this comment.
As discussed, let's return SUCCEEDED for now, but open an issue to change it soon.
| if (!collision_checker_->isCollisionFree(pose2d)) { | ||
| stopRobot(); | ||
| RCLCPP_INFO(node_->get_logger(), "Collision Ahead -Exiting Spin "); | ||
| return Status::SUCCEEDED; |
There was a problem hiding this comment.
I think we should return FAILED, the action server will set the goal as aborted in that case.
8ab347d to
1337c4f
Compare
… for collision checker
615776e to
da4f12c
Compare
| { | ||
| costmap_pub_->on_activate(); | ||
| costmap_update_pub_->on_activate(); | ||
| costmap_raw_pub_->on_activate(); |
There was a problem hiding this comment.
No, I wouldn't specify who I expect to be listening. Just keep it to a topic name that anyone can listen to.
| nav2_util::LifecycleNode::SharedPtr node, | ||
| std::string & topic_name); | ||
|
|
||
| CostmapSubscriber( |
There was a problem hiding this comment.
If we expect it to be a class that might have multiple clients, then I would include them. If it's done specifically for just one module implementation, you can probably skip it.
| collision_checker_ = std::make_unique<nav2_costmap_2d::CollisionChecker>( | ||
| costmap_sub_, footprint_sub_, get_name()); | ||
|
|
||
| get_robot_pose_service_ = rclcpp_node_->create_service<nav2_msgs::srv::GetRobotPose>( |
| if (!collision_checker_->isCollisionFree(pose2d)) { | ||
| stopRobot(); | ||
| RCLCPP_INFO(node_->get_logger(), "Collision Ahead -Exiting Spin "); | ||
| return Status::SUCCEEDED; |
There was a problem hiding this comment.
Ok with me to return FAILED for now, but we should address this ASAP.
* Cleanup package.xml und clarify tests of JTC. * Update joint_trajectory_controller/package.xml Co-authored-by: Bence Magyar <bence.magyar.robotics@gmail.com> --------- Co-authored-by: Bence Magyar <bence.magyar.robotics@gmail.com>
This PR requires #737.
Basic Info
Description of contribution in a few bullet points
Future work that may be required in bullet points