moveit2
The MoveIt Motion Planning Framework for ROS 2.
joystick_servo_example.cpp
Go to the documentation of this file.
1 /*********************************************************************
2  * Software License Agreement (BSD License)
3  *
4  * Copyright (c) 2020, PickNik Inc.
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 names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  *********************************************************************/
34 
35 /* Title : joystick_servo_example.cpp
36  * Project : moveit_servo
37  * Created : 08/07/2020
38  * Author : Adam Pettinger
39  */
40 
41 #include <sensor_msgs/msg/joy.hpp>
42 #include <geometry_msgs/msg/twist_stamped.hpp>
43 #include <control_msgs/msg/joint_jog.hpp>
44 #include <std_srvs/srv/trigger.hpp>
45 #include <moveit_msgs/msg/planning_scene.hpp>
46 #include <rclcpp/client.hpp>
47 #include <rclcpp/experimental/buffers/intra_process_buffer.hpp>
48 #include <rclcpp/node.hpp>
49 #include <rclcpp/publisher.hpp>
50 #include <rclcpp/qos.hpp>
51 #include <rclcpp/qos_event.hpp>
52 #include <rclcpp/subscription.hpp>
53 #include <rclcpp/time.hpp>
54 #include <rclcpp/utilities.hpp>
55 #include <thread>
56 
57 // We'll just set up parameters here
58 const std::string JOY_TOPIC = "/joy";
59 const std::string TWIST_TOPIC = "/servo_node/delta_twist_cmds";
60 const std::string JOINT_TOPIC = "/servo_node/delta_joint_cmds";
61 const std::string EEF_FRAME_ID = "panda_hand";
62 const std::string BASE_FRAME_ID = "panda_link0";
63 
64 // Enums for button names -> axis/button array index
65 // For XBOX 1 controller
66 enum Axis
67 {
74  D_PAD_X = 6,
75  D_PAD_Y = 7
76 };
77 enum Button
78 {
79  A = 0,
80  B = 1,
81  X = 2,
82  Y = 3,
86  MENU = 7,
87  HOME = 8,
90 };
91 
92 // Some axes have offsets (e.g. the default trigger position is 1.0 not 0)
93 // This will map the default values for the axes
94 std::map<Axis, double> AXIS_DEFAULTS = { { LEFT_TRIGGER, 1.0 }, { RIGHT_TRIGGER, 1.0 } };
95 std::map<Button, double> BUTTON_DEFAULTS;
96 
97 // To change controls or setup a new controller, all you should to do is change the above enums and the follow 2
98 // functions
106 bool convertJoyToCmd(const std::vector<float>& axes, const std::vector<int>& buttons,
107  std::unique_ptr<geometry_msgs::msg::TwistStamped>& twist,
108  std::unique_ptr<control_msgs::msg::JointJog>& joint)
109 {
110  // Give joint jogging priority because it is only buttons
111  // If any joint jog command is requested, we are only publishing joint commands
112  if (buttons[A] || buttons[B] || buttons[X] || buttons[Y] || axes[D_PAD_X] || axes[D_PAD_Y])
113  {
114  // Map the D_PAD to the proximal joints
115  joint->joint_names.push_back("panda_joint1");
116  joint->velocities.push_back(axes[D_PAD_X]);
117  joint->joint_names.push_back("panda_joint2");
118  joint->velocities.push_back(axes[D_PAD_Y]);
119 
120  // Map the diamond to the distal joints
121  joint->joint_names.push_back("panda_joint7");
122  joint->velocities.push_back(buttons[B] - buttons[X]);
123  joint->joint_names.push_back("panda_joint6");
124  joint->velocities.push_back(buttons[Y] - buttons[A]);
125  return false;
126  }
127 
128  // The bread and butter: map buttons to twist commands
129  twist->twist.linear.z = axes[RIGHT_STICK_Y];
130  twist->twist.linear.y = axes[RIGHT_STICK_X];
131 
132  double lin_x_right = -0.5 * (axes[RIGHT_TRIGGER] - AXIS_DEFAULTS.at(RIGHT_TRIGGER));
133  double lin_x_left = 0.5 * (axes[LEFT_TRIGGER] - AXIS_DEFAULTS.at(LEFT_TRIGGER));
134  twist->twist.linear.x = lin_x_right + lin_x_left;
135 
136  twist->twist.angular.y = axes[LEFT_STICK_Y];
137  twist->twist.angular.x = axes[LEFT_STICK_X];
138 
139  double roll_positive = buttons[RIGHT_BUMPER];
140  double roll_negative = -1 * (buttons[LEFT_BUMPER]);
141  twist->twist.angular.z = roll_positive + roll_negative;
142 
143  return true;
144 }
145 
150 void updateCmdFrame(std::string& frame_name, const std::vector<int>& buttons)
151 {
152  if (buttons[CHANGE_VIEW] && frame_name == EEF_FRAME_ID)
153  frame_name = BASE_FRAME_ID;
154  else if (buttons[MENU] && frame_name == BASE_FRAME_ID)
155  frame_name = EEF_FRAME_ID;
156 }
157 
158 namespace moveit_servo
159 {
160 class JoyToServoPub : public rclcpp::Node
161 {
162 public:
163  JoyToServoPub(const rclcpp::NodeOptions& options)
164  : Node("joy_to_twist_publisher", options), frame_to_publish_(BASE_FRAME_ID)
165  {
166  // Setup pub/sub
167  joy_sub_ = this->create_subscription<sensor_msgs::msg::Joy>(
168  JOY_TOPIC, rclcpp::SystemDefaultsQoS(),
169  [this](const sensor_msgs::msg::Joy::ConstSharedPtr& msg) { return joyCB(msg); });
170 
171  twist_pub_ = this->create_publisher<geometry_msgs::msg::TwistStamped>(TWIST_TOPIC, rclcpp::SystemDefaultsQoS());
172  joint_pub_ = this->create_publisher<control_msgs::msg::JointJog>(JOINT_TOPIC, rclcpp::SystemDefaultsQoS());
173  collision_pub_ =
174  this->create_publisher<moveit_msgs::msg::PlanningScene>("/planning_scene", rclcpp::SystemDefaultsQoS());
175 
176  // Create a service client to start the ServoNode
177  servo_start_client_ = this->create_client<std_srvs::srv::Trigger>("/servo_node/start_servo");
178  servo_start_client_->wait_for_service(std::chrono::seconds(1));
179  servo_start_client_->async_send_request(std::make_shared<std_srvs::srv::Trigger::Request>());
180 
181  // Load the collision scene asynchronously
182  collision_pub_thread_ = std::thread([this]() {
183  rclcpp::sleep_for(std::chrono::seconds(3));
184  // Create collision object, in the way of servoing
185  moveit_msgs::msg::CollisionObject collision_object;
186  collision_object.header.frame_id = "panda_link0";
187  collision_object.id = "box";
188 
189  shape_msgs::msg::SolidPrimitive table_1;
190  table_1.type = table_1.BOX;
191  table_1.dimensions = { 0.4, 0.6, 0.03 };
192 
193  geometry_msgs::msg::Pose table_1_pose;
194  table_1_pose.position.x = 0.6;
195  table_1_pose.position.y = 0.0;
196  table_1_pose.position.z = 0.4;
197 
198  shape_msgs::msg::SolidPrimitive table_2;
199  table_2.type = table_2.BOX;
200  table_2.dimensions = { 0.6, 0.4, 0.03 };
201 
202  geometry_msgs::msg::Pose table_2_pose;
203  table_2_pose.position.x = 0.0;
204  table_2_pose.position.y = 0.5;
205  table_2_pose.position.z = 0.25;
206 
207  collision_object.primitives.push_back(table_1);
208  collision_object.primitive_poses.push_back(table_1_pose);
209  collision_object.primitives.push_back(table_2);
210  collision_object.primitive_poses.push_back(table_2_pose);
211  collision_object.operation = collision_object.ADD;
212 
213  moveit_msgs::msg::PlanningSceneWorld psw;
214  psw.collision_objects.push_back(collision_object);
215 
216  auto ps = std::make_unique<moveit_msgs::msg::PlanningScene>();
217  ps->world = psw;
218  ps->is_diff = true;
219  collision_pub_->publish(std::move(ps));
220  });
221  }
222 
223  ~JoyToServoPub() override
224  {
225  if (collision_pub_thread_.joinable())
226  collision_pub_thread_.join();
227  }
228 
229  void joyCB(const sensor_msgs::msg::Joy::ConstSharedPtr& msg)
230  {
231  // Create the messages we might publish
232  auto twist_msg = std::make_unique<geometry_msgs::msg::TwistStamped>();
233  auto joint_msg = std::make_unique<control_msgs::msg::JointJog>();
234 
235  // This call updates the frame for twist commands
236  updateCmdFrame(frame_to_publish_, msg->buttons);
237 
238  // Convert the joystick message to Twist or JointJog and publish
239  if (convertJoyToCmd(msg->axes, msg->buttons, twist_msg, joint_msg))
240  {
241  // publish the TwistStamped
242  twist_msg->header.frame_id = frame_to_publish_;
243  twist_msg->header.stamp = this->now();
244  twist_pub_->publish(std::move(twist_msg));
245  }
246  else
247  {
248  // publish the JointJog
249  joint_msg->header.stamp = this->now();
250  joint_msg->header.frame_id = "panda_link3";
251  joint_pub_->publish(std::move(joint_msg));
252  }
253  }
254 
255 private:
256  rclcpp::Subscription<sensor_msgs::msg::Joy>::SharedPtr joy_sub_;
257  rclcpp::Publisher<geometry_msgs::msg::TwistStamped>::SharedPtr twist_pub_;
258  rclcpp::Publisher<control_msgs::msg::JointJog>::SharedPtr joint_pub_;
259  rclcpp::Publisher<moveit_msgs::msg::PlanningScene>::SharedPtr collision_pub_;
260  rclcpp::Client<std_srvs::srv::Trigger>::SharedPtr servo_start_client_;
261 
262  std::string frame_to_publish_;
263 
264  std::thread collision_pub_thread_;
265 }; // class JoyToServoPub
266 
267 } // namespace moveit_servo
268 
269 // Register the component with class_loader
270 #include <rclcpp_components/register_node_macro.hpp>
271 RCLCPP_COMPONENTS_REGISTER_NODE(moveit_servo::JoyToServoPub)
JoyToServoPub(const rclcpp::NodeOptions &options)
void joyCB(const sensor_msgs::msg::Joy::ConstSharedPtr &msg)
@ LEFT_STICK_CLICK
@ RIGHT_STICK_CLICK
std::map< Button, double > BUTTON_DEFAULTS
const std::string JOY_TOPIC
const std::string EEF_FRAME_ID
std::map< Axis, double > AXIS_DEFAULTS
void updateCmdFrame(std::string &frame_name, const std::vector< int > &buttons)
// This should update the frame_to_publish_ as needed for changing command frame via controller
const std::string BASE_FRAME_ID
bool convertJoyToCmd(const std::vector< float > &axes, const std::vector< int > &buttons, std::unique_ptr< geometry_msgs::msg::TwistStamped > &twist, std::unique_ptr< control_msgs::msg::JointJog > &joint)
// This converts a joystick axes and buttons array to a TwistStamped or JointJog message
const std::string TWIST_TOPIC
const std::string JOINT_TOPIC