-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Check for collisions in motion primitives #889
Check for collisions in motion primitives #889
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the line break for these three methods?
@@ -77,11 +78,13 @@ class Costmap2DPublisher | |||
{ | |||
costmap_pub_->on_activate(); | |||
costmap_update_pub_->on_activate(); | |||
costmap_raw_pub_->on_activate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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_pubto
costmap_rviz_pub`?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, np.
nav2_msgs::msg::Costmap | ||
toCostmapMsg(nav2_costmap_2d::Costmap2D * costmap) | ||
{ | ||
double resolution = costmap->getResolution(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I like that.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should is case be considered successful? We weren't able to fully execute the spin.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok with me to return FAILED for now, but we should address this ASAP.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review in-progress...
#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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if getRobotPose()
is something we want from the CollisionChecker
, maybe better to make it private.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider references instead of pointers.
} | ||
|
||
bool | ||
CollisionChecker::getRobotPose(geometry_msgs::msg::Pose & current_pose) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method can be const
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to get the pointed object.
return line_cost; | ||
} | ||
|
||
double CollisionChecker::pointCost(int x, int y) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method can be const
return footprint_cost; | ||
} | ||
|
||
double CollisionChecker::lineCost(int x0, int x1, int y0, int y1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this method can be const
|
||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
line_cost
could be declared inside the for loop
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would we want to do that here?
adding test cases move collision checker to nav2_costmap_2d
revert dwb_critics cmake changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe make this a method.
@@ -59,6 +59,8 @@ Costmap2DPublisher::Costmap2DPublisher( | |||
// 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", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use node_ instead
rclcpp::node_interfaces::NodeLoggingInterface::SharedPtr node_logging_; | ||
|
||
void toCostmap2D(); | ||
void costmap_callback(const nav2_msgs::msg::Costmap::SharedPtr msg); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
costmapCallback
public: | ||
explicit CollisionCheckerException(const std::string description) | ||
: std::runtime_error(description) {} | ||
typedef std::shared_ptr<CollisionCheckerException> Ptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typedef not used here
@@ -45,8 +45,8 @@ Status BackUp::onRun(const std::shared_ptr<const BackUpAction::Goal> command) | |||
|
|||
command_x_ = command->target.x; | |||
|
|||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively use the GetRobotPoseClient
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So we duplicate the GetRobotPoseClient
here and in the collision_checker
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this RCLCPP_WARN
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return FAILED
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@@ -77,11 +78,13 @@ class Costmap2DPublisher | |||
{ | |||
costmap_pub_->on_activate(); | |||
costmap_update_pub_->on_activate(); | |||
costmap_raw_pub_->on_activate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, np.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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