moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
move_group_interface_cpp_test.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2020, Tyler Weaver
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of PickNik Inc. nor the
18 * names of its contributors may be used to endorse or promote
19 * products derived from this software without specific prior
20 * written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 *********************************************************************/
35
36/* Author: Tyler Weaver, Boston Cleek */
37
38/* These integration tests are based on the tutorials for using move_group:
39 * https://moveit.github.io/moveit_tutorials/doc/move_group_interface/move_group_interface_tutorial.html
40 */
41
42// C++
43#include <string>
44#include <vector>
45#include <map>
46#include <future>
47
48// ROS
49#include <ros/ros.h>
50
51// The Testing Framework and Utils
52#include <gtest/gtest.h>
53
54// MoveIt
59
60// TF2
61#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
62#include <tf2_eigen/tf2_eigen.hpp>
63
64// 10um accuracy tested for position and orientation
65constexpr double EPSILON = 1e-5;
66
67static const std::string PLANNING_GROUP = "panda_arm";
68constexpr double PLANNING_TIME_S = 30.0;
69constexpr double MAX_VELOCITY_SCALE = 1.0;
70constexpr double MAX_ACCELERATION_SCALE = 1.0;
71constexpr double GOAL_TOLERANCE = 1e-6;
72
73class MoveGroupTestFixture : public ::testing::Test
74{
75public:
76 void SetUp() override
77 {
78 nh_ = ros::NodeHandle("/move_group_interface_cpp_test");
79 move_group_ = std::make_shared<moveit::planning_interface::MoveGroupInterface>(PLANNING_GROUP);
80
81 // set velocity and acceleration scaling factors (full speed)
82 move_group_->setMaxVelocityScalingFactor(MAX_VELOCITY_SCALE);
83 move_group_->setMaxAccelerationScalingFactor(MAX_ACCELERATION_SCALE);
84
85 // allow more time for planning
86 move_group_->setPlanningTime(PLANNING_TIME_S);
87
88 // set the tolerance for the goals to be smaller than epsilon
89 move_group_->setGoalTolerance(GOAL_TOLERANCE);
90
91 /* the tf buffer is not strictly needed,
92 but it's a simple way to add the codepaths to the tests */
93 psm_ = std::make_shared<planning_scene_monitor::PlanningSceneMonitor>("robot_description",
95 psm_->startSceneMonitor("/move_group/monitored_planning_scene");
96 psm_->requestPlanningSceneState();
97
98 // give move_group_, planning_scene_interface_ and psm_ time to connect their topics
99 ros::Duration(0.5).sleep();
100 }
101
102 // run updater() and ensure at least one geometry update was processed by the `move_group` node after doing so
103 void synchronizeGeometryUpdate(const std::function<void()>& updater)
104 {
105 SCOPED_TRACE("synchronizeGeometryUpdate");
106 std::promise<void> promise;
107 std::future<void> future = promise.get_future();
108 psm_->addUpdateCallback([this, &promise](planning_scene_monitor::PlanningSceneMonitor::SceneUpdateType t) {
110 promise.set_value();
111 psm_->clearUpdateCallbacks();
112 });
113 updater();
114 // the updater must have triggered a geometry update, otherwise we can't be sure about the state of the scene anymore
115 ASSERT_EQ(future.wait_for(std::chrono::seconds(5)), std::future_status::ready);
116 }
117
118 void planAndMoveToPose(const geometry_msgs::Pose& pose)
119 {
120 SCOPED_TRACE("planAndMoveToPose");
121 ASSERT_TRUE(move_group_->setJointValueTarget(pose));
122 planAndMove();
123 }
124
126 {
127 SCOPED_TRACE("planAndMove");
129 ASSERT_EQ(move_group_->plan(my_plan), moveit::core::MoveItErrorCode::SUCCESS);
130 ASSERT_EQ(move_group_->move(), moveit::core::MoveItErrorCode::SUCCESS);
131 }
132
133 void testEigenPose(const Eigen::Isometry3d& expected, const Eigen::Isometry3d& actual)
134 {
135 SCOPED_TRACE("testEigenPose");
136 std::stringstream ss;
137 ss << "expected: \n" << expected.matrix() << "\nactual: \n" << actual.matrix();
138 EXPECT_TRUE(actual.isApprox(expected, EPSILON)) << ss.str();
139 }
140
141 void testPose(const Eigen::Isometry3d& expected_pose)
142 {
143 SCOPED_TRACE("testPose(const Eigen::Isometry3d&)");
144 // get the pose of the end effector link after the movement
145 geometry_msgs::PoseStamped actual_pose_stamped = move_group_->getCurrentPose();
146 Eigen::Isometry3d actual_pose;
147 tf2::fromMsg(actual_pose_stamped.pose, actual_pose);
148
149 // compare to planned pose
150 testEigenPose(expected_pose, actual_pose);
151 }
152
153 void testPose(const geometry_msgs::Pose& expected_pose_msg)
154 {
155 SCOPED_TRACE("testPose(const geometry_msgs::Pose&)");
156 Eigen::Isometry3d expected_pose;
157 tf2::fromMsg(expected_pose_msg, expected_pose);
158 testPose(expected_pose);
159 }
160
161 void testJointPositions(const std::vector<double>& expected)
162 {
163 SCOPED_TRACE("testJointPositions");
164 const moveit::core::JointModelGroup* joint_model_group =
165 move_group_->getCurrentState()->getJointModelGroup(PLANNING_GROUP);
166 std::vector<double> actual;
167 move_group_->getCurrentState()->copyJointGroupPositions(joint_model_group, actual);
168 ASSERT_EQ(expected.size(), actual.size());
169 for (size_t i = 0; i < actual.size(); ++i)
170 {
171 double delta = std::abs(expected[i] - actual[i]);
172 EXPECT_LT(delta, EPSILON) << "joint index: " << i << ", plan: " << expected[i] << ", result: " << actual[i];
173 }
174 }
175
176protected:
177 ros::NodeHandle nh_;
178 moveit::planning_interface::MoveGroupInterfacePtr move_group_;
180 planning_scene_monitor::PlanningSceneMonitorPtr psm_;
181};
182
183TEST_F(MoveGroupTestFixture, PathConstraintCollisionTest)
184{
185 SCOPED_TRACE("PathConstraintCollisionTest");
186
188 // set a custom start state
189 // this simplifies planning for the orientation constraint below
190 geometry_msgs::Pose start_pose;
191 start_pose.orientation.w = 1.0;
192 start_pose.position.x = 0.3;
193 start_pose.position.y = 0.0;
194 start_pose.position.z = 0.6;
195 planAndMoveToPose(start_pose);
196
198 // Test setting target pose with eigen and with geometry_msgs
199 geometry_msgs::Pose target_pose;
200 target_pose.orientation.w = 1.0;
201 target_pose.position.x = 0.3;
202 target_pose.position.y = -0.3;
203 target_pose.position.z = 0.6;
204
205 // convert to eigen
206 Eigen::Isometry3d eigen_target_pose;
207 tf2::fromMsg(target_pose, eigen_target_pose);
208
209 // set with eigen, get ros message representation
210 move_group_->setPoseTarget(eigen_target_pose);
211 geometry_msgs::PoseStamped set_target_pose = move_group_->getPoseTarget();
212 Eigen::Isometry3d eigen_set_target_pose;
213 tf2::fromMsg(set_target_pose.pose, eigen_set_target_pose);
214
215 // expect that they are identical
216 testEigenPose(eigen_target_pose, eigen_set_target_pose);
217
219 // create an orientation constraint
220 moveit_msgs::OrientationConstraint ocm;
221 ocm.link_name = move_group_->getEndEffectorLink();
222 ocm.header.frame_id = move_group_->getPlanningFrame();
223 ocm.orientation.w = 1.0;
224 ocm.absolute_x_axis_tolerance = 0.1;
225 ocm.absolute_y_axis_tolerance = 0.1;
226 ocm.absolute_z_axis_tolerance = 0.1;
227 ocm.weight = 1.0;
228 moveit_msgs::Constraints test_constraints;
229 test_constraints.orientation_constraints.push_back(ocm);
230 move_group_->setPathConstraints(test_constraints);
231
233 // plan and move
234 planAndMove();
235
236 // get the pose after the movement
237 testPose(eigen_target_pose);
238
239 // clear path constraints
240 move_group_->clearPathConstraints();
241
242 // move back to ready pose
243 move_group_->setNamedTarget("ready");
244 planAndMove();
245}
246
247TEST_F(MoveGroupTestFixture, ModifyPlanningSceneAsyncInterfaces)
248{
250 // Define a collision object ROS message.
251 moveit_msgs::CollisionObject collision_object;
252 collision_object.header.frame_id = move_group_->getPlanningFrame();
253
254 // The id of the object is used to identify it.
255 collision_object.id = "box1";
256
257 // Define a box to add to the world.
258 shape_msgs::SolidPrimitive primitive;
259 primitive.type = primitive.BOX;
260 primitive.dimensions.resize(3);
261 primitive.dimensions[0] = 0.1;
262 primitive.dimensions[1] = 1.0;
263 primitive.dimensions[2] = 1.0;
264
265 // Define a pose for the box (specified relative to frame_id)
266 geometry_msgs::Pose box_pose;
267 box_pose.orientation.w = 1.0;
268 box_pose.position.x = 0.5;
269 box_pose.position.y = 0.0;
270 box_pose.position.z = 0.5;
271
272 collision_object.primitives.push_back(primitive);
273 collision_object.primitive_poses.push_back(box_pose);
274 collision_object.operation = collision_object.ADD;
275
276 std::vector<moveit_msgs::CollisionObject> collision_objects;
277 collision_objects.push_back(collision_object);
278
279 // Now, let's add the collision object into the world
280 synchronizeGeometryUpdate([&]() { planning_scene_interface_.addCollisionObjects(collision_objects); });
281
282 // attach and detach collision object
283 synchronizeGeometryUpdate([&]() { EXPECT_TRUE(move_group_->attachObject(collision_object.id)); });
284 EXPECT_EQ(planning_scene_interface_.getAttachedObjects().size(), std::size_t(1));
285 synchronizeGeometryUpdate([&]() { EXPECT_TRUE(move_group_->detachObject(collision_object.id)); });
286 EXPECT_EQ(planning_scene_interface_.getAttachedObjects().size(), std::size_t(0));
287
288 // remove object from world
289 const std::vector<std::string> object_ids = { collision_object.id };
290 EXPECT_EQ(planning_scene_interface_.getObjects().size(), std::size_t(1));
291 synchronizeGeometryUpdate([&]() { planning_scene_interface_.removeCollisionObjects(object_ids); });
292 EXPECT_EQ(planning_scene_interface_.getObjects().size(), std::size_t(0));
293}
294
296{
297 SCOPED_TRACE("CartPathTest");
298
299 // Plan from current pose
300 const geometry_msgs::PoseStamped start_pose = move_group_->getCurrentPose();
301
302 std::vector<geometry_msgs::Pose> waypoints;
303 waypoints.push_back(start_pose.pose);
304
305 geometry_msgs::Pose target_waypoint = start_pose.pose;
306 target_waypoint.position.z -= 0.2;
307 waypoints.push_back(target_waypoint); // down
308
309 target_waypoint.position.y -= 0.2;
310 waypoints.push_back(target_waypoint); // right
311
312 target_waypoint.position.z += 0.2;
313 target_waypoint.position.y += 0.2;
314 target_waypoint.position.x -= 0.2;
315 waypoints.push_back(target_waypoint); // up and left
316
317 moveit_msgs::RobotTrajectory trajectory;
318 const auto eef_step = 0.01;
319
320 // test below is meaningless if Cartesian planning did not succeed
321 ASSERT_GE(EPSILON + move_group_->computeCartesianPath(waypoints, eef_step, trajectory), 1.0);
322
323 // Execute trajectory
324 EXPECT_EQ(move_group_->execute(trajectory), moveit::core::MoveItErrorCode::SUCCESS);
325
326 // get the pose after the movement
327 testPose(target_waypoint);
328}
329
330TEST_F(MoveGroupTestFixture, JointSpaceGoalTest)
331{
332 SCOPED_TRACE("JointSpaceGoalTest");
333
334 // Next get the current set of joint values for the group.
335 std::vector<double> plan_joint_positions;
336 move_group_->getCurrentState()->copyJointGroupPositions(
337 move_group_->getCurrentState()->getJointModelGroup(PLANNING_GROUP), plan_joint_positions);
338
339 // Now, let's modify the joint positions. (radians)
340 ASSERT_EQ(plan_joint_positions.size(), std::size_t(7));
341 plan_joint_positions = { 1.2, -1.0, -0.1, -2.4, 0.0, 1.5, 0.6 };
342 move_group_->setJointValueTarget(plan_joint_positions);
343
344 // plan and move
345 planAndMove();
346
347 // test that we moved to the expected joint positions
348 testJointPositions(plan_joint_positions);
349}
350
351int main(int argc, char** argv)
352{
353 ros::init(argc, argv, "move_group_interface_cpp_test");
354 testing::InitGoogleTest(&argc, argv);
355
356 ros::AsyncSpinner spinner(1);
357 spinner.start();
358
359 int result = RUN_ALL_TESTS();
360 return result;
361}
void synchronizeGeometryUpdate(const std::function< void()> &updater)
void testJointPositions(const std::vector< double > &expected)
void testPose(const geometry_msgs::Pose &expected_pose_msg)
void testPose(const Eigen::Isometry3d &expected_pose)
void testEigenPose(const Eigen::Isometry3d &expected, const Eigen::Isometry3d &actual)
moveit::planning_interface::MoveGroupInterfacePtr move_group_
moveit::planning_interface::PlanningSceneInterface planning_scene_interface_
planning_scene_monitor::PlanningSceneMonitorPtr psm_
void planAndMoveToPose(const geometry_msgs::Pose &pose)
@ UPDATE_GEOMETRY
The geometry of the scene was updated. This includes receiving new octomaps, collision objects,...
constexpr double PLANNING_TIME_S
constexpr double MAX_ACCELERATION_SCALE
int main(int argc, char **argv)
constexpr double GOAL_TOLERANCE
constexpr double EPSILON
constexpr double MAX_VELOCITY_SCALE
TEST_F(MoveGroupTestFixture, PathConstraintCollisionTest)
std::shared_ptr< tf2_ros::Buffer > getSharedTF()
The representation of a motion plan (as ROS messages)