[Draft/ReferenceOnly] Port move_group node to ROS2#187
[Draft/ReferenceOnly] Port move_group node to ROS2#187ddengster wants to merge 26 commits intomoveit:masterfrom ddengster:port_training
Conversation
| 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) |
| #include <pluginlib/class_list_macros.hpp> | ||
|
|
||
| PLUGINLIB_EXPORT_CLASS( | ||
| ompl_interface::OMPLPlannerManager, planning_interface::PlannerManager) No newline at end of file |
There was a problem hiding this comment.
made OMPLPlannerManager switch to plugin system
| 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) |
There was a problem hiding this comment.
replace rclcpp::spin_until_future_complete
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
name conflict. Suggest replacement to be "move_action" instead of "move_group"
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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())); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
should not use rclcpp::spin_until_future_complete
| return res.result->error_code; | ||
| } | ||
|
|
||
| MoveItErrorCode execute(const Plan& plan, bool wait) |
There was a problem hiding this comment.
function of major interest
| return pick(constructPickupGoal(object.id, std::move(response->grasps), plan_only)); | ||
| } | ||
|
|
||
| MoveItErrorCode plan(Plan& plan) |
There was a problem hiding this comment.
function of major interest
|
@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.
In general, we do fresh package/library migrations piece by piece so full pipeline functionality is not required out of the box.
Nice!
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.
I fully agree, this is a pain point for which we still need to discuss some best practices.
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.
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? |
|
Great effort @ddengster, thanks for sharing this and we look forward to getting it merged! |
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? |
henningkayser
left a comment
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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_); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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}) |
| PROPERTIES | ||
| VERSION "${${PROJECT_NAME}_VERSION}") | ||
|
|
||
| # Causes the visibility macros to use dllexport rather than dllimport, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Interesting, conflict with what exactly? In general I don't see anything wrong with renaming this, though.
| { | ||
| } | ||
|
|
||
| void setNode(const rclcpp::Node::SharedPtr& node); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
I see, it's really super long. I would probably just remove the "default_capabilities" in the middle, actually.
I've sent the email |
…unported action clients
|
closing since move_group is merged |
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: