cob_hand_bridge_node.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9 
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
19 #include <cob_hand_bridge/Status.h>
20 
21 #include <std_srvs/Trigger.h>
22 
23 #include <sensor_msgs/JointState.h>
24 #include <control_msgs/FollowJointTrajectoryAction.h>
25 
27 
29 
30 #include <ros/ros.h>
31 
32 #include <boost/thread/mutex.hpp>
33 #include <boost/shared_ptr.hpp>
34 #include <boost/scoped_ptr.hpp>
35 
36 #include <angles/angles.h>
37 
38 boost::mutex g_mutex;
39 
40 cob_hand_bridge::Status::ConstPtr g_status;
42 
44 sensor_msgs::JointState g_js;
45 
47 
50 
53 boost::scoped_ptr<FollowJointTrajectoryActionServer> g_as;
54 control_msgs::FollowJointTrajectoryGoalConstPtr g_goal;
65 std::vector<double> g_goal_tolerance;
66 
68  return g_status && (g_status->status & (g_status->MASK_FINGER_READY | g_status->MASK_ERROR)) == g_status->MASK_FINGER_READY && g_status->rc == 0;
69 }
70 
71 bool checkAction_nolock(bool deadline_exceeded){
72  control_msgs::FollowJointTrajectoryResult result;
73  if(g_as->isActive()){
74  bool goal_reached = false;
75  if(g_motion_stopped) {
76  goal_reached = true;;
77  for(size_t i = 0; i < g_status->joints.position_cdeg.size(); ++i){
78  if(fabs(g_status->joints.position_cdeg[i]-g_command.position_cdeg[i]) > g_goal_tolerance[i]){
79  goal_reached = false;
80  result.error_code = result.GOAL_TOLERANCE_VIOLATED;
81  break;
82  }
83  }
84  }
85  if(!isFingerReady_nolock()) {
86  g_deadline_timer.stop();
87  g_as->setAborted();
88  }else if(g_motion_stopped && (goal_reached || g_motors_moved)) {
89  g_deadline_timer.stop();
90  g_as->setSucceeded(result);
91  }else if (deadline_exceeded) {
92  g_deadline_timer.stop();
93  result.error_code = result.GOAL_TOLERANCE_VIOLATED;
94  g_as->setAborted(result, "goal not reached in time");
95  return false;
96  }
97  }
98  return true;
99 }
100 
102 {
103  boost::mutex::scoped_lock lock(g_mutex);
104 
105  ros::NodeHandle nh_priv("~");
106 
107  if(!g_status) {
108  res.message = "hand is not yet connected";
109  }else if(g_status->status == g_status->NOT_INITIALIZED) {
110  lock.unlock();
112  srv.request.port = nh_priv.param<std::string>("sdhx/port", "/dev/ttyACM0");
113  srv.request.min_pwm0 = nh_priv.param("sdhx/min_pwm0", 0);
114  srv.request.min_pwm1 = nh_priv.param("sdhx/min_pwm1", 0);
115  srv.request.max_pwm0 = nh_priv.param("sdhx/max_pwm0", 0);
116  srv.request.max_pwm1 = nh_priv.param("sdhx/max_pwm1", 0);
117 
118  if(g_init_finger_client.waitForExistence(ros::Duration(nh_priv.param("sdhx/connect_timeout", 10)))){
119  if(!g_init_finger_client.call(srv)) return false;
120  res.success = srv.response.success;
121  if(res.success) g_as->start();
122  }else{
123  res.message = "init_finger service does not exist";
124  }
125  lock.lock();
126  g_initialized = res.success;
127  }else if(!g_initialized){
128  g_as->start();
129  res.success = true;
130  res.message = "finger already initialized, restarting the controller";
131  g_initialized = true;
132  }else{
133  res.success = true;
134  res.message = "already initialized";
135  }
136 
137  return true;
138 }
139 
140 void statusCallback(const cob_hand_bridge::Status::ConstPtr& msg){
141  boost::mutex::scoped_lock lock(g_mutex);
142  double dt = g_js.header.stamp.toSec() - msg->stamp.toSec();
143  bool first = !g_status;
144  bool calc_vel = !first && dt != 0 ;
145  g_status = msg;
146  g_topic_status->tick(msg->stamp);
147 
148  g_motion_stopped = true;
149  g_control_stopped = true;
150 
151  if(msg->status & msg->MASK_FINGER_READY){
152  for(size_t i=0; i < msg->joints.position_cdeg.size(); ++i){
153  double new_pos = angles::from_degrees(msg->joints.position_cdeg[i]/100.0);
154  if(calc_vel){
155  g_js.velocity[i] = (new_pos - g_js.position[i]) / dt;
156  }
157  if(fabs(g_js.velocity[i]) > g_stopped_velocity){
158  g_motion_stopped = false;
159  g_motors_moved = true;
160  }
161  if(fabs(msg->joints.current_100uA[i]) > g_stopped_current){
162  g_control_stopped = false;
163  }
164  g_js.position[i] = new_pos;
165  }
166  g_js.header.stamp = msg->stamp;
167  g_js_pub.publish(g_js);
168  }
169  checkAction_nolock(false);
170 
171  if(first) g_init_srv = ros::NodeHandle("driver").advertiseService("init", initCallback);
172 }
173 
175  boost::mutex::scoped_lock lock(g_mutex);
176 
177  if(!g_status){
178  stat.summary(stat.ERROR, "not connected");
179  return;
180  }
181 
182  if(g_status->status == g_status->NOT_INITIALIZED){
183  stat.summary(stat.WARN, "not initialized");
184  }else if(g_status->status & g_status->MASK_ERROR){
185  stat.summary(stat.ERROR, "Bridge has error");
186  }else{
187  stat.summary(stat.OK, "Connected and running");
188  }
189  stat.add("sdhx_ready", bool(g_status->status & g_status->MASK_FINGER_READY));
190  stat.add("sdhx_rc", uint32_t(g_status->rc));
191  stat.add("sdhx_motion_stopped", g_motion_stopped);
192  stat.add("sdhx_control_stopped", g_control_stopped);
193 
194  if(g_status->rc > 0){
195  stat.mergeSummary(stat.ERROR, "SDHx has error");
196  }
197 }
198 
199 void callHalt(){
200  if(g_halt_client.exists()){
201  std_srvs::Trigger srv;
202  if(!g_halt_client.call(srv) || !srv.response.success){
203  ROS_ERROR("Halt service did not succeed");
204  }
205  }else{
206  ROS_ERROR("Halt service is not available");
207  }
208 }
209 
211  boost::mutex::scoped_lock lock(g_mutex);
212  if(g_as->isActive()){
213  if(!checkAction_nolock(true)){
214  g_command.position_cdeg = g_status->joints.position_cdeg;
215  lock.unlock();
216  callHalt();
217  lock.lock();
218  }
219  }
220 }
221 
222 void goalCB() {
223 
224  control_msgs::FollowJointTrajectoryGoalConstPtr goal = g_as->acceptNewGoal();
225 
226  g_deadline_timer.stop();
227 
228  // goal is invalid if goal has more than 2 (or 0) points. If 2 point, the first needs time_from_start to be 0
229  control_msgs::FollowJointTrajectoryResult result;
230  result.error_code = result.INVALID_GOAL;
231  if(goal->trajectory.points.size()!=1 && (goal->trajectory.points.size()!=2 || !goal->trajectory.points[0].time_from_start.isZero() ) ){
232  g_as->setAborted(result, "goal is not valid");
233  return;
234  }
235 
237  size_t found = 0;
238 
239  for(size_t i=0; i < g_js.name.size(); ++i){
240  for(size_t j=0; j < goal->trajectory.joint_names.size(); ++j){
241  if(g_js.name[i] == goal->trajectory.joint_names[j]){
242  new_command.position_cdeg[i]= angles::to_degrees(goal->trajectory.points.back().positions[j])*100; // cdeg to rad
243 
244  if(goal->trajectory.points.back().effort.size() > 0){
245  if(goal->trajectory.points.back().effort.size() == new_command.current_100uA.size()){
246  new_command.current_100uA[i] = goal->trajectory.points.back().effort[j] * 1000.0; // (A -> 100uA)
247  }else{
248  g_as->setAborted(result, "Number of effort values mismatch");
249  return;
250  }
251  }
252 
253  ++found;
254  break;
255  }
256  }
257  }
258 
259  if(found != g_js.name.size()){
260  g_as->setAborted(result, "Joint names mismatch");
261  return;
262  }
263  std::vector<double> goal_tolerance(g_command.position_cdeg.size(), angles::to_degrees(g_stopped_velocity)*100); // assume 1s movement is allowed, cdeg to rad
264 
265  for(size_t i = 0; i < goal->goal_tolerance.size(); ++i){
266  bool missing = true;
267  for(size_t j=0; j < g_js.name.size(); ++j){
268  if(goal->goal_tolerance[i].name == g_js.name[j]){
269  missing = false;
270  if(goal->goal_tolerance[i].position > 0.0){
271  goal_tolerance[j] = goal->goal_tolerance[i].position;
272  }
273  break;
274  }
275  }
276  if(missing){
277  g_as->setAborted(result, "Goal tolerance invalid");
278  return;
279  }
280  }
281 
282  ros::Time now = ros::Time::now();
283  ros::Time trajectory_deadline = (goal->trajectory.header.stamp.isZero() ? now : goal->trajectory.header.stamp)
284  + goal->trajectory.points.back().time_from_start + ros::Duration(goal->goal_time_tolerance);
285  if(trajectory_deadline <= now){
286  result.error_code = result.OLD_HEADER_TIMESTAMP;
287  g_as->setAborted(result, "goal is not valid");
288  return;
289  }
290 
291  boost::mutex::scoped_lock lock(g_mutex);
292 
293  if(!isFingerReady_nolock()) {
294  g_as->setAborted(result, "SDHx is not ready for commands");
295  return;
296  }
297 
298  g_command = new_command;
299 
300  g_goal_tolerance = goal_tolerance;
301  g_motors_moved = false;
302  g_command_timer.stop();
303  g_command_pub.publish(g_command);
304  g_deadline_timer.setPeriod(trajectory_deadline-ros::Time::now());
305  g_deadline_timer.start();
306  g_command_timer.start();
307 }
308 
309 void cancelCB() {
310  boost::mutex::scoped_lock lock(g_mutex);
311  g_command.position_cdeg = g_status->joints.position_cdeg;
312  g_deadline_timer.stop();
313  lock.unlock();
314 
315  callHalt();
316  g_as->setPreempted();
317 }
318 
320  boost::mutex::scoped_lock lock(g_mutex);
321  if(isFingerReady_nolock()){
322  if(g_control_stopped || !g_motion_stopped) g_command_pub.publish(g_command);
323  } else {
324  g_command_timer.stop();
325  lock.unlock();
326  callHalt();
327  ROS_WARN("finger is not ready, stopped resend timer");
328  }
329 }
330 
331 int main(int argc, char* argv[])
332 {
333  ros::init(argc, argv, "cob_hand_bridge_node");
334 
335  ros::NodeHandle nh;
336  ros::NodeHandle nh_d("driver");
337  ros::NodeHandle nh_i("internal");
338  ros::NodeHandle nh_priv("~");
339 
340  if(!nh_priv.getParam("sdhx/joint_names", g_js.name)){
341  ROS_ERROR("Please provide joint names for SDHx");
342  return 1;
343  }
344 
345  if(g_command.position_cdeg.size() != g_js.name.size()){
346  ROS_ERROR_STREAM("Number of joints does not match " << g_command.position_cdeg.size());
347  return 1;
348  }
349 
350 
351  nh_priv.param("sdhx/stopped_velocity",g_stopped_velocity, 0.05);
352  if(g_stopped_velocity <= 0.0){
353  ROS_ERROR_STREAM("stopped_velocity must be a positive number");
354  return 1;
355  }
356 
357  double stopped_current= nh_priv.param("sdhx/stopped_current", 0.1);
358  if(stopped_current <= 0.0){
359  ROS_ERROR_STREAM("stopped_current must be a positive number");
360  return 1;
361  }
362  g_stopped_current = stopped_current * 1000.0; // (A -> 100uA)
363 
364  std::vector<double> default_currents;
365  if(nh_priv.getParam("sdhx/default_currents", default_currents)){
366  if(default_currents.size() != g_default_command.current_100uA.size()){
367  ROS_ERROR_STREAM("Number of current values does not match number of joints");
368  return 1;
369  }
370  for(size_t i=0; i< g_default_command.current_100uA.size(); ++i){
371  g_default_command.current_100uA[i] = default_currents[i] * 1000.0; // (A -> 100uA)
372  }
373  }else{
374  g_default_command.current_100uA[0] = 2120;
375  g_default_command.current_100uA[1] = 1400;
376  }
377 
378  g_default_command.velocity_cdeg_s[0] = 1000;
379  g_default_command.velocity_cdeg_s[1] = 1000;
380 
381  g_js.position.resize(g_command.position_cdeg.size());
382  g_js.velocity.resize(g_command.position_cdeg.size());
383 
384  diagnostic_updater::Updater diag_updater;
385  diag_updater.setHardwareID(nh_priv.param("hardware_id", std::string("none")));
386  diag_updater.add("bridge", reportDiagnostics);
387 
389  nh_priv.param("status/min_duration", -1.0),
390  nh_priv.param("status/max_duration", 0.1)
391  );
392  g_topic_status.reset ( new diagnostic_updater::TimeStampStatus(param) );
393  diag_updater.add("connection", boost::bind(&diagnostic_updater::TimeStampStatus::run, g_topic_status, _1));
394 
395  ros::Timer diag_timer = nh.createTimer(ros::Duration(diag_updater.getPeriod()/2.0),boost::bind(&diagnostic_updater::Updater::update, &diag_updater));
396 
397  double resend_period = nh_priv.param("sdhx/resend_period", 0.1);
398  if(resend_period > 0.0) g_command_timer = nh.createTimer(ros::Duration(resend_period), resendCommand, false, false);
399 
400  g_deadline_timer = nh.createTimer(ros::Duration(1.0), handleDeadline, true, false); // period is not used, will be overwritten
401 
402  g_js_pub = nh.advertise<sensor_msgs::JointState>("joint_states", 1);
403 
404  ros::Subscriber status_sub = nh_i.subscribe("status", 1, statusCallback);
405  g_init_finger_client = nh_i.serviceClient<cob_hand_bridge::InitFinger>("init_finger");
406  g_command_pub = nh_i.advertise<cob_hand_bridge::JointValues>("command", 1);
407  g_halt_client = nh_d.serviceClient<std_srvs::Trigger>("halt");
408 
409  g_as.reset(new FollowJointTrajectoryActionServer(ros::NodeHandle("joint_trajectory_controller"), "follow_joint_trajectory",false));
410  g_as->registerPreemptCallback(cancelCB);
411  g_as->registerGoalCallback(goalCB);
412 
413  ros::spin();
414  return 0;
415 }
ServiceClient serviceClient(const std::string &service_name, bool persistent=false, const M_string &header_values=M_string())
control_msgs::FollowJointTrajectoryGoalConstPtr g_goal
boost::scoped_ptr< FollowJointTrajectoryActionServer > g_as
bool param(const std::string &param_name, T &param_val, const T &default_val)
const char * message
Definition: Trigger.h:59
void publish(const boost::shared_ptr< M > &message) const
ros::ServiceClient g_init_finger_client
Definition: time.h:30
ros::Timer g_command_timer
void summary(unsigned char lvl, const std::string s)
Subscriber subscribe(const std::string &topic, uint32_t queue_size, void(T::*fp)(M), T *obj, const TransportHints &transport_hints=TransportHints())
actionlib::SimpleActionServer< control_msgs::FollowJointTrajectoryAction > FollowJointTrajectoryActionServer
bool g_motion_stopped
ros::ServiceServer g_init_srv
void setHardwareID(const std::string &hwid)
sensor_msgs::JointState g_js
void stop()
void setPeriod(const Duration &period, bool reset=true)
ROSCPP_DECL void init(int &argc, char **argv, const std::string &name, uint32_t options=0)
bool call(MReq &req, MRes &res)
boost::shared_ptr< diagnostic_updater::TimeStampStatus > g_topic_status
void start()
int main(int argc, char *argv[])
void add(const std::string &name, TaskFunction f)
cob_hand_bridge::JointValues g_default_command
bool g_motors_moved
NodeHandle_< EmbeddedLinuxHardware > NodeHandle
Definition: ros.h:32
virtual void run(diagnostic_updater::DiagnosticStatusWrapper &stat)
#define ROS_WARN(...)
bool g_control_stopped
double g_stopped_velocity
ros::Publisher g_command_pub
void goalCB()
double g_stopped_current
ros::ServiceClient g_halt_client
std::vector< double > g_goal_tolerance
bool waitForExistence(ros::Duration timeout=ros::Duration(-1))
static double from_degrees(double degrees)
bool param(const std::string &param_name, T &param_val, const T &default_val) const
void callHalt()
Timer createTimer(Rate r, Handler h, Obj o, bool oneshot=false, bool autostart=true) const
static double to_degrees(double radians)
ros::Timer g_deadline_timer
cob_hand_bridge::JointValues g_command
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
ROSCPP_DECL void spin()
ros::Publisher g_js_pub
bool initCallback(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res)
void cancelCB()
bool isFingerReady_nolock()
void handleDeadline(const ros::TimerEvent &)
void statusCallback(const cob_hand_bridge::Status::ConstPtr &msg)
void mergeSummary(unsigned char lvl, const std::string s)
void reportDiagnostics(diagnostic_updater::DiagnosticStatusWrapper &stat)
bool getParam(const std::string &key, std::string &s) const
static Time now()
void add(const std::string &key, const T &val)
bool advertiseService(ServiceServer< MReq, MRes > &srv)
Definition: node_handle.h:348
#define ROS_ERROR_STREAM(args)
boost::mutex g_mutex
bool checkAction_nolock(bool deadline_exceeded)
cob_hand_bridge::Status::ConstPtr g_status
#define ROS_ERROR(...)
bool g_initialized
void resendCommand(const ros::TimerEvent &e)


cob_hand_bridge
Author(s): Mathias Lüdtke
autogenerated on Tue Oct 20 2020 03:35:57