Skip to content

[Draft/ReferenceOnly] Port move_group node to ROS2#187

Closed
ddengster wants to merge 26 commits intomoveit:masterfrom
ddengster:port_training
Closed

[Draft/ReferenceOnly] Port move_group node to ROS2#187
ddengster wants to merge 26 commits intomoveit:masterfrom
ddengster:port_training

Conversation

@ddengster
Copy link
Copy Markdown
Contributor

@ddengster ddengster commented May 13, 2020

This PR is done in conjunction with a 'coffeebot' gazebo simulation in which I use move_group to plan and execute trajectories. There will be some stuff missing that should be in the port, I'll try my best to highlight them. Feel free to siphon changes from this PR into the main branch as I will be trying to get the pipeline that decides how to get the poses into the srdf file working before I start merging changes from this PR.

There are other dependencies that have features that needed to be ported to ROS2. These are in the PRs:

Description

Port move_group node to ROS2. Submodules like capabilities have also been ported. Submodules regarding sensors have not been ported

Design problems regarding nodes:

  • Parameters and parameter file handling. Move_group used to have a number of classes that share parameters from a central ROS1 server. I've put them into the move_group node and that node flows throughout different classes in the codebase. The side effect is that you will get a number of deadlocks because that node is also used for subscribing and listening to the same topic. To fix this you'll have create or supply a different node. (see TrajectoryExecutionManager::initialize)

  • There's a good number of functions that uses rclcpp::spin_until_future_complete. It unfortunately gives me exceptions ('Node already added to executor') when I already have an existing executor to manage the execution of these nodes for me (see move_group.cpp main function). I recommend not using spin_until_future_complete.

To summarize, having separate nodes for each class has its pros and cons. This PR's style is having 1 node to contain all parameters and making new nodes and supplying them parameters as needed.

Modifications to launch and config files generated by moveit_setup_assistant:

  • There's a ton of xml launch files that I ported to a single python launch file just so things could be easier. In my experience the ROS2 xml launch repo isnt too stable for my liking, and xml is a data format ill-suited to the task that launching programs require. I recommend porting to python launch files.

  • config files are prepended with the following as listed in the ROS2 launch wiki:

<node name, usually 'move_group'>:
  ros__parameters:

std::string value;
if (node_->get_parameter(group_name_param + "." + k, value))
rclcpp::Parameter param = node_->get_parameter(group_name_param + "." + k);
if (param.get_type() == rclcpp::ParameterType::PARAMETER_STRING)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same fix mentioned in issue #184

#include <pluginlib/class_list_macros.hpp>

PLUGINLIB_EXPORT_CLASS(
ompl_interface::OMPLPlannerManager, planning_interface::PlannerManager) No newline at end of file
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made OMPLPlannerManager switch to plugin system

@ddengster ddengster changed the title Draft/ReferenceOnly Port move_group node to ROS2 [Draft/ReferenceOnly] Port move_group node to ROS2 May 13, 2020
auto cancel_result_future = controller_action_client_->async_cancel_goal(current_goal_);
//@todo: replace rclcpp::spin_until_future_complete
if (rclcpp::spin_until_future_complete(node_, cancel_result_future) !=
rclcpp::executor::FutureReturnCode::SUCCESS)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace rclcpp::spin_until_future_complete

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for std::shared_future results from async calls we should just use wait() wait_for() or wait_until() since the executor is already doing the spinning.

static const std::string SET_PLANNER_PARAMS_SERVICE_NAME =
"set_planner_params"; // service name to set planner parameters
static const std::string MOVE_ACTION = "move_group"; // name of 'move' action
static const std::string MOVE_ACTION = "move_action"; // name of 'move' action
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

name conflict. Suggest replacement to be "move_action" instead of "move_group"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, conflict with what exactly? In general I don't see anything wrong with renaming this, though.

rclcpp::NodeOptions opt;
opt.allow_undeclared_parameters(true);
opt.automatically_declare_parameters_from_overrides(true);
rclcpp::Node::SharedPtr nh = rclcpp::Node::make_shared("move_group", opt);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the main function, and our main node is "move_group". We supply it with parameters and pass this node throughout the codebase.

std::string robot_desc_param = "robot_description";
std::string str = getParameterFromRemoteNode<std::string>(nh, "robot_state_publisher", robot_desc_param);
nh->declare_parameter(robot_desc_param);
nh->set_parameter(rclcpp::Parameter(robot_desc_param, str));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The robot_description parameter is fetched from robot_state_publisher. Not sure if #169 will help fix it? Also I've had to reuse this code to supply the robot_description parameter to my other codebases.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to clarify 'my other codebases' meaning the codebases that use MoveGroupInterface or PlanningSceneInterface

RCLCPP_INFO(LOGGER, "%s", semantic_file.c_str());
std::stringstream buffer;
buffer << file.rdbuf();
nh->set_parameter(rclcpp::Parameter("robot_description_semantic", buffer.str()));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the above, I've had to reuse this code to supply the robot_description_semantic parameter to my other codebases. Not sure if there's a better way of doing it.

for (auto param : allparams)
{
//RCLCPP_INFO(LOGGER, "%s", param.first.c_str());
controller_mgr_node_->set_parameter(rclcpp::Parameter(param.first, param.second));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for a quick hack we copy parameters from move_group to the controller_mgr node. I'd imagine something more specific with code standards.

// only report that execution finished successfully when the robot actually stopped moving
if (last_execution_status_ == moveit_controller_manager::ExecutionStatus::SUCCEEDED)
waitForRobotToStop(*trajectories_[i - 1]);
waitForRobotToStop(*trajectories_[i - 1], 10);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

increasing wait times from default 1 to 10. Not share if we can have a parameter instead?

//@todo: switch out for callbacks
int64_t timeout = 3;
if (rclcpp::spin_until_future_complete(node_, future, std::chrono::seconds(timeout)) !=
rclcpp::executor::FutureReturnCode::SUCCESS)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should not use rclcpp::spin_until_future_complete

return res.result->error_code;
}

MoveItErrorCode execute(const Plan& plan, bool wait)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function of major interest

return pick(constructPickupGoal(object.id, std::move(response->grasps), plan_only));
}

MoveItErrorCode plan(Plan& plan)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function of major interest

@henningkayser
Copy link
Copy Markdown
Member

@ddengster first of all, great work and thanks a lot for your effort! We were actually about to spin-up development again to make progress towards the full release, so I'm really appreciating your contributions.

This PR is done in conjunction with a 'coffeebot' gazebo simulation in which I use move_group to plan and execute trajectories. There will be some stuff missing that should be in the port, I'll try my best to highlight them. Feel free to siphon changes from this PR into the main branch as I will be trying to get the pipeline that decides how to get the poses into the srdf file working before I start merging changes from this PR.

In general, we do fresh package/library migrations piece by piece so full pipeline functionality is not required out of the box.

There are other dependencies that have features that needed to be ported to ROS2. These are in the PRs:

Nice!

Description

Port move_group node to ROS2. Submodules like capabilities have also been ported. Submodules regarding sensors have not been ported

Design problems regarding nodes:

  • Parameters and parameter file handling. Move_group used to have a number of classes that share parameters from a central ROS1 server. I've put them into the move_group node and that node flows throughout different classes in the codebase. The side effect is that you will get a number of deadlocks because that node is also used for subscribing and listening to the same topic. To fix this you'll have create or supply a different node. (see TrajectoryExecutionManager::initialize)

Yes, with MoveGroup we're now at a point where we have to make some non-trivial design decisions. I see two basic approaches: 1. Implement a "global" parameter server that resembles the ROS1 pattern; 2. Refactor parameter propagation between classes and enable parameter syncing between nodes that require it. We're about to investigate these and related changes the next weeks.

  • There's a good number of functions that uses rclcpp::spin_until_future_complete. It unfortunately gives me exceptions ('Node already added to executor') when I already have an existing executor to manage the execution of these nodes for me (see move_group.cpp main function). I recommend not using spin_until_future_complete.

I fully agree, this is a pain point for which we still need to discuss some best practices.

To summarize, having separate nodes for each class has its pros and cons. This PR's style is having 1 node to contain all parameters and making new nodes and supplying them parameters as needed.

Absolutely the right approach for now. Our goal is to first have a full straight port which can be synced with MoveIt 1 and only then we start making further ROS2 related refactorings.

Modifications to launch and config files generated by moveit_setup_assistant:

  • There's a ton of xml launch files that I ported to a single python launch file just so things could be easier. In my experience the ROS2 xml launch repo isnt too stable for my liking, and xml is a data format ill-suited to the task that launching programs require. I recommend porting to python launch files.
  • config files are prepended with the following as listed in the ROS2 launch wiki:
<node name, usually 'move_group'>:
  ros__parameters:

Using python launch files is the way to go IMO. That means that we might have to redesign some parts of the MSA which should also include considerations about a potential multi-node setup or how we want to design parameter lookup.

Right now I'm working on a development roadmap and scheduling weekly sync meetings to get the project kicked-off. Since your contributions appear like you are working on this more long-term, are you interested in joining the meetings to get introduced and collaborate on some of the points discussed here?

@davetcoleman
Copy link
Copy Markdown
Member

Great effort @ddengster, thanks for sharing this and we look forward to getting it merged!

@ddengster
Copy link
Copy Markdown
Contributor Author

Since your contributions appear like you are working on this more long-term, are you interested in joining the meetings to get introduced and collaborate on some of the points discussed here?

Yea I'd like to know who's working on what, and to give you a heads-up as to what I'm working on so as to reduce conflicts. Thing is I'm on GMT+8 Singapore time and might not be able to attend US timings. Do you have a google doc for the meetings so I can at least write down what I'm working on atm or read what's happening?

@henningkayser
Copy link
Copy Markdown
Member

Since your contributions appear like you are working on this more long-term, are you interested in joining the meetings to get introduced and collaborate on some of the points discussed here?

Yea I'd like to know who's working on what, and to give you a heads-up as to what I'm working on so as to reduce conflicts. Thing is I'm on GMT+8 Singapore time and might not be able to attend US timings. Do you have a google doc for the meetings so I can at least write down what I'm working on atm or read what's happening?

We don't have the meeting schedule fixed yet, but it will share plans and results publicly here on Github. I'm at GMT+2 myself, but maybe we can find a time that works for all. Could you send me a mail so that we can sync up?

Copy link
Copy Markdown
Member

@henningkayser henningkayser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty solid overall, great work! I'll give this a couple runs the next days.

auto cancel_result_future = controller_action_client_->async_cancel_goal(current_goal_);
//@todo: replace rclcpp::spin_until_future_complete
if (rclcpp::spin_until_future_complete(node_, cancel_result_future) !=
rclcpp::executor::FutureReturnCode::SUCCESS)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, for std::shared_future results from async calls we should just use wait() wait_for() or wait_until() since the executor is already doing the spinning.

rclcpp::executor::FutureReturnCode::SUCCESS);

rclcpp::Time start_time = node_->now();
auto end_time = start_time + timeout;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. Also, the default timeout = rclcpp::Duration(0) should indicate an infinite timeout


if (done_)
RCLCPP_DEBUG_STREAM(LOGGER, "sending trajectory to " << name_);
RCLCPP_INFO_STREAM(LOGGER, "sending trajectory to " << name_);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change the log level?

// Send goal
auto current_goal_future = controller_action_client_->async_send_goal(goal, send_goal_options);
if (rclcpp::spin_until_future_complete(node_, current_goal_future) != rclcpp::executor::FutureReturnCode::SUCCESS)
std::future_status status = current_goal_future.wait_for(std::chrono::seconds(60));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this timeout be the expected execution time + some margin?

set_target_properties(${MOVEIT_LIB_NAME} PROPERTIES VERSION "${${PROJECT_NAME}_VERSION}")

target_link_libraries(${MOVEIT_LIB_NAME} moveit_pick_place_planner ${catkin_LIBRARIES} ${Boost_LIBRARIES})
target_link_libraries(${MOVEIT_LIB_NAME} moveit_pick_place_planner ${LIBS} ${Boost_LIBRARIES})
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Boost still required?

PROPERTIES
VERSION "${${PROJECT_NAME}_VERSION}")

# Causes the visibility macros to use dllexport rather than dllimport,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume you're working on Windows? ;)

RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION})
install(TARGETS moveit_move_group_capabilities_base
moveit_move_group_default_capabilities
ARCHIVE
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit, but I think it reads a little better with the old format

static const std::string SET_PLANNER_PARAMS_SERVICE_NAME =
"set_planner_params"; // service name to set planner parameters
static const std::string MOVE_ACTION = "move_group"; // name of 'move' action
static const std::string MOVE_ACTION = "move_action"; // name of 'move' action
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, conflict with what exactly? In general I don't see anything wrong with renaming this, though.

{
}

void setNode(const rclcpp::Node::SharedPtr& node);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and setContext() are only called once before initialize(), right? I would pass node and context as parameters of initialize() and remove the setters (similar to how the planning adapters are initialized now).

namespace move_group
{

static const rclcpp::Logger LOGGER = rclcpp::get_logger("moveit_move_group_default_capabilities.apply_planning_scene_service_capability");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, it's really super long. I would probably just remove the "default_capabilities" in the middle, actually.

@ddengster
Copy link
Copy Markdown
Contributor Author

Could you send me a mail so that we can sync up?

I've sent the email

@ddengster
Copy link
Copy Markdown
Contributor Author

closing since move_group is merged

@ddengster ddengster closed this Aug 31, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants