urg_node_driver.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2013, Willow Garage, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * * Redistributions in binary form must reproduce the above copyright
11  * notice, this list of conditions and the following disclaimer in the
12  * documentation and/or other materials provided with the distribution.
13  * * Neither the name of the Willow Garage, Inc. nor the names of its
14  * contributors may be used to endorse or promote products derived from
15  * this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27  * POSSIBILITY OF SUCH DAMAGE.
28  */
29 
30 /*
31  * Author: Chad Rockey, Michael Carroll, Mike O'Driscoll
32  */
33 
35 
36 #include <tf/tf.h> // tf header for resolving tf prefix
37 #include <string>
38 #include <diagnostic_msgs/AddDiagnostics.h>
39 #include <diagnostic_msgs/DiagnosticStatus.h>
40 #include <urg_node/Status.h>
41 
42 namespace urg_node
43 {
44 
45 // Useful typedefs
47 
49  nh_(nh),
50  pnh_(private_nh)
51 {
52  initSetup();
53 }
54 
56  nh_(),
57  pnh_("~")
58 {
59  initSetup();
60 }
61 
63 {
64  close_diagnostics_ = true;
65  close_scan_ = true;
66  service_yield_ = false;
67 
68  error_code_ = 0;
69  lockout_status_ = false;
70  // Initialize node and nodehandles
71 
72  // Get parameters so we can change these later.
73  pnh_.param<std::string>("ip_address", ip_address_, "");
74  pnh_.param<int>("ip_port", ip_port_, 10940);
75  pnh_.param<std::string>("serial_port", serial_port_, "/dev/ttyACM0");
76  pnh_.param<int>("serial_baud", serial_baud_, 115200);
77  pnh_.param<bool>("calibrate_time", calibrate_time_, false);
78  pnh_.param<bool>("synchronize_time", synchronize_time_, false);
79  pnh_.param<bool>("publish_intensity", publish_intensity_, true);
80  pnh_.param<bool>("publish_multiecho", publish_multiecho_, false);
81  pnh_.param<int>("error_limit", error_limit_, 4);
82  pnh_.param<double>("diagnostics_tolerance", diagnostics_tolerance_, 0.05);
83  pnh_.param<double>("diagnostics_window_time", diagnostics_window_time_, 5.0);
84  pnh_.param<bool>("get_detailed_status", detailed_status_, false);
85 
86  // Set up publishers and diagnostics updaters, we only need one
87  if (publish_multiecho_)
88  {
90  }
91  else
92  {
93  laser_pub_ = nh_.advertise<sensor_msgs::LaserScan>("scan", 20);
94  }
95 
96  status_service_ = nh_.advertiseService("update_laser_status", &UrgNode::statusCallback, this);
97  status_pub_ = nh_.advertise<urg_node::Status>("laser_status", 1, true);
98 
100  diagnostic_updater_->add("Hardware Status", this, &UrgNode::populateDiagnosticsStatus);
101 }
102 
104 {
105  if (diagnostics_thread_.joinable())
106  {
107  // Clean up our diagnostics thread.
108  close_diagnostics_ = true;
109  diagnostics_thread_.join();
110  }
111  if (scan_thread_.joinable())
112  {
113  close_scan_ = true;
114  scan_thread_.join();
115  }
116 }
117 
119 {
120  bool result = false;
121  service_yield_ = true;
122  boost::mutex::scoped_lock lock(lidar_mutex_);
123 
124  if (urg_)
125  {
126  device_status_ = urg_->getSensorStatus();
127 
128  if (detailed_status_)
129  {
130  URGStatus status;
131  if (urg_->getAR00Status(status))
132  {
133  urg_node::Status msg;
134  msg.operating_mode = status.operating_mode;
135  msg.error_status = status.error_status;
136  msg.error_code = status.error_code;
137  msg.lockout_status = status.lockout_status;
138 
140  error_code_ = status.error_code;
141 
142  UrgDetectionReport report;
143  if (urg_->getDL00Status(report))
144  {
145  msg.area_number = report.area;
146  msg.distance = report.distance;
147  msg.angle = report.angle;
148  }
149  else
150  {
151  ROS_WARN("Failed to get detection report.");
152  }
153 
154  // Publish the status on the latched topic for inspection.
155  status_pub_.publish(msg);
156  result = true;
157  }
158  else
159  {
160  ROS_WARN("Failed to retrieve status");
161 
162  urg_node::Status msg;
163  status_pub_.publish(msg);
164  }
165  }
166  }
167  return result;
168 }
169 
170 bool UrgNode::statusCallback(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res)
171 {
172  ROS_INFO("Got update lidar status callback");
173  res.success = false;
174  res.message = "Laser not ready";
175 
176  if (updateStatus())
177  {
178  res.message = "Status retrieved";
179  res.success = true;
180  }
181  else
182  {
183  res.message = "Failed to update status";
184  res.success = false;
185  }
186 
187  return true;
188 }
189 
190 bool UrgNode::reconfigure_callback(urg_node::URGConfig& config, int level)
191 {
192  if (!urg_)
193  {
194  ROS_ERROR("Reconfigure failed, not ready");
195  return false;
196  }
197 
198  if (level < 0) // First call, initialize, laser not yet started
199  {
200  urg_->setAngleLimitsAndCluster(config.angle_min, config.angle_max, config.cluster);
201  urg_->setSkip(config.skip);
202  }
203  else if (level > 0) // Must stop
204  {
205  urg_->stop();
206  ROS_INFO("Stopped data due to reconfigure.");
207 
208  // Change values that required stopping
209  urg_->setAngleLimitsAndCluster(config.angle_min, config.angle_max, config.cluster);
210  urg_->setSkip(config.skip);
211 
212  try
213  {
214  urg_->start();
215  ROS_INFO("Streaming data after reconfigure.");
216  }
217  catch (std::runtime_error& e)
218  {
219  ROS_FATAL("%s", e.what());
220  ros::Duration(1.0).sleep();
221  ros::shutdown();
222  return false;
223  }
224  }
225 
226  // The publish frequency changes based on the number of skipped scans.
227  // Update accordingly here.
228  freq_min_ = 1.0 / (urg_->getScanPeriod() * (config.skip + 1));
229 
230  std::string frame_id = tf::resolve(config.tf_prefix, config.frame_id);
231  urg_->setFrameId(frame_id);
232  urg_->setUserLatency(config.time_offset);
233 
234  return true;
235 }
236 
238 {
239  if (!urg_ || !srv_)
240  {
241  ROS_DEBUG_THROTTLE(10, "Unable to update reconfigure limits. Not Ready.");
242  return;
243  }
244 
245  urg_node::URGConfig min, max;
246  srv_->getConfigMin(min);
247  srv_->getConfigMax(max);
248 
249  min.angle_min = urg_->getAngleMinLimit();
250  min.angle_max = min.angle_min;
251  max.angle_max = urg_->getAngleMaxLimit();
252  max.angle_min = max.angle_max;
253 
254  srv_->setConfigMin(min);
255  srv_->setConfigMax(max);
256 }
257 
259 {
260  boost::mutex::scoped_lock lock(lidar_mutex_);
261  if (!urg_)
262  {
263  ROS_DEBUG_THROTTLE(10, "Unable to calibrate time offset. Not Ready.");
264  return;
265  }
266  try
267  {
268  // Don't let outside interruption effect lidar offset.
269  ROS_INFO("Starting calibration. This will take a few seconds.");
270  ROS_WARN("Time calibration is still experimental.");
271  ros::Duration latency = urg_->computeLatency(10);
272  ROS_INFO("Calibration finished. Latency is: %.4f.", latency.toSec());
273  }
274  catch (std::runtime_error& e)
275  {
276  ROS_FATAL("Could not calibrate time offset: %s", e.what());
277  ros::Duration(1.0).sleep();
278  ros::shutdown();
279  }
280 }
281 
282 // Add diagnostic analyzers to the diagnostic aggregator
284 {
285  std::string node_namespace = ros::this_node::getNamespace();
286  std::string node_prefix = "";
287  std::string node_name = ros::this_node::getName().substr(1);
288  std::string node_path;
289  std::string node_id = node_name;
290 
291  // Create "Fake" Namespace for Diagnostics
292  if (node_namespace == "/")
293  {
294  node_namespace = ros::this_node::getName();
295  node_prefix = ros::this_node::getName() + "/";
296  }
297 
298  // Sanitize Node ID
299  size_t pos = node_id.find("/");
300  while (pos != std::string::npos)
301  {
302  node_id.replace(pos, 1, "_");
303  pos = node_id.find("/");
304  }
305 
306  // Sanitize Node Path
307  node_path = node_id;
308  pos = node_path.find("_");
309  while (pos != std::string::npos)
310  {
311  node_path.replace(pos, 1, " ");
312  pos = node_path.find("_");
313  }
314 
315  // GroupAnalyzer Parameters
316  if (!ros::param::has(node_prefix + "analyzers/hokuyo/path"))
317  {
318  ros::param::set(node_prefix + "analyzers/hokuyo/path", "Hokuyo");
319  ros::param::set(node_prefix + "analyzers/hokuyo/type", "diagnostic_aggregator/AnalyzerGroup");
320  }
321 
322  // Analyzer Parameters
323  std::string analyzerPath = node_prefix + "analyzers/hokuyo/analyzers/" + node_id;
324  if (!ros::param::has(analyzerPath + "/path"))
325  {
326  ros::param::set(analyzerPath + "/path", node_path);
327  ros::param::set(analyzerPath + "/type", "diagnostic_aggregator/GenericAnalyzer");
328  ros::param::set(analyzerPath + "/startswith", node_name);
329  ros::param::set(analyzerPath + "/remove_prefix", node_name);
330  }
331 
332  // Bond to Diagnostics Aggregator
333  if (bond_ == nullptr)
334  {
335  bond_ = boost::shared_ptr<bond::Bond>(new bond::Bond("/diagnostics_agg/bond" + node_namespace, node_namespace));
336  }
337  else if (!bond_->isBroken())
338  {
339  return;
340  }
341  bond_->setConnectTimeout(120);
342 
343  // Call AddDiagnostics Service
344  diagnostic_msgs::AddDiagnostics srv;
345  srv.request.load_namespace = node_namespace;
346  if (!ros::service::waitForService("/diagnostics_agg/add_diagnostics", 1000))
347  {
348  return;
349  }
350  bond_->start();
351  ros::service::call("/diagnostics_agg/add_diagnostics", srv);
352 }
353 
354 // Diagnostics update task to be run in a thread.
356 {
357  addDiagnostics();
358  while (!close_diagnostics_)
359  {
360  diagnostic_updater_->update();
361  boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
362  }
363 }
364 
365 // Populate a diagnostics status message.
367 {
368  if (!urg_)
369  {
370  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR,
371  "Not Connected");
372  return;
373  }
374 
375  if (!urg_->getIPAddress().empty())
376  {
377  stat.add("IP Address", urg_->getIPAddress());
378  stat.add("IP Port", urg_->getIPPort());
379  }
380  else
381  {
382  stat.add("Serial Port", urg_->getSerialPort());
383  stat.add("Serial Baud", urg_->getSerialBaud());
384  }
385 
386  if (!urg_->isStarted())
387  {
388  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR,
389  "Not Connected: " + device_status_);
390  }
391  else if (device_status_ != std::string("Sensor works well.") &&
392  device_status_ != std::string("Stable 000 no error.") &&
393  device_status_ != std::string("sensor is working normally"))
394  {
395  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR,
396  "Abnormal status: " + device_status_);
397  }
398  else if (error_code_ != 0)
399  {
400  stat.summaryf(diagnostic_msgs::DiagnosticStatus::ERROR,
401  "Lidar reporting error code: %X",
402  error_code_);
403  }
404  else if (lockout_status_)
405  {
406  stat.summary(diagnostic_msgs::DiagnosticStatus::ERROR,
407  "Lidar locked out.");
408  }
409  else
410  {
411  stat.summary(diagnostic_msgs::DiagnosticStatus::OK,
412  "Streaming");
413  }
414 
415  stat.add("Vendor Name", vendor_name_);
416  stat.add("Product Name", product_name_);
417  stat.add("Firmware Version", firmware_version_);
418  stat.add("Firmware Date", firmware_date_);
419  stat.add("Protocol Version", protocol_version_);
420  stat.add("Device ID", device_id_);
421  stat.add("Computed Latency", urg_->getComputedLatency());
422  stat.add("User Time Offset", urg_->getUserTimeOffset());
423 
424  // Things not explicitly required by REP-0138, but still interesting.
425  stat.add("Device Status", device_status_);
426  stat.add("Scan Retrieve Error Count", error_count_);
427 
428  stat.add("Lidar Error Code", error_code_);
429  stat.add("Locked out", lockout_status_);
430 }
431 
433 {
434  // Don't let external access to retrieve
435  // status during the connection process.
436  boost::mutex::scoped_lock lock(lidar_mutex_);
437 
438  try
439  {
440  urg_.reset(); // Clear any previous connections();
441  if (!ip_address_.empty())
442  {
445  }
446  else
447  {
450  }
451 
452  std::stringstream ss;
453  ss << "Connected to";
454  if (publish_multiecho_)
455  {
456  ss << " multiecho";
457  }
458  if (!ip_address_.empty())
459  {
460  ss << " network";
461  }
462  else
463  {
464  ss << " serial";
465  }
466  ss << " device with";
467  if (publish_intensity_)
468  {
469  ss << " intensity and";
470  }
471  ss << " ID: " << urg_->getDeviceID();
472  ROS_INFO_STREAM(ss.str());
473 
474  device_status_ = urg_->getSensorStatus();
475  vendor_name_ = urg_->getVendorName();
476  product_name_ = urg_->getProductName();
477  firmware_version_ = urg_->getFirmwareVersion();
478  firmware_date_ = urg_->getFirmwareDate();
479  protocol_version_ = urg_->getProtocolVersion();
480  device_id_ = urg_->getDeviceID();
481 
482  if (diagnostic_updater_ && urg_)
483  {
484  diagnostic_updater_->setHardwareID(urg_->getDeviceID());
485  }
486 
487  return true;
488  }
489  catch (std::runtime_error& e)
490  {
491  ROS_ERROR_THROTTLE(10.0, "Error connecting to Hokuyo: %s", e.what());
492  urg_.reset();
493  return false;
494  }
495  catch (std::exception& e)
496  {
497  ROS_ERROR_THROTTLE(10.0, "Unknown error connecting to Hokuyo: %s", e.what());
498  urg_.reset();
499  return false;
500  }
501 
502  return false;
503 }
504 
506 {
507  while (!close_scan_)
508  {
509  if (!urg_)
510  {
511  if (!connect())
512  {
513  boost::this_thread::sleep(boost::posix_time::milliseconds(500));
514  continue; // Connect failed, sleep, try again.
515  }
516  }
517 
518  // Configure limits (Must do this after creating the urgwidget)
520 
521  if (calibrate_time_)
522  {
524  }
525 
526  // Clear the dynamic reconfigure server
527  srv_.reset();
528  // Spin once to de-register it's services before making a new
529  // service next.
530  ros::spinOnce();
531 
532  if (!urg_ || !ros::ok)
533  {
534  continue;
535  }
536  else
537  {
538  // Set up dynamic reconfigure
539  srv_.reset(new dynamic_reconfigure::Server<urg_node::URGConfig>(pnh_));
540  // Configure limits (Must do this after creating the urgwidget)
542  srv_->setCallback(boost::bind(&UrgNode::reconfigure_callback, this, _1, _2));
543  }
544 
545  // Before starting, update the status
546  updateStatus();
547 
548  // Start the urgwidget
549  try
550  {
551  // If the connection failed, don't try and connect
552  // pointer is invalid.
553  if (!urg_)
554  {
555  continue; // Return to top of main loop, not connected.
556  }
557  device_status_ = urg_->getSensorStatus();
558  urg_->start();
559  ROS_INFO("Streaming data.");
560  // Clear the error count.
561  error_count_ = 0;
562  }
563  catch (std::runtime_error& e)
564  {
565  ROS_ERROR_THROTTLE(10.0, "Error starting Hokuyo: %s", e.what());
566  urg_.reset();
567  ros::Duration(1.0).sleep();
568  continue; // Return to top of main loop
569  }
570  catch (...)
571  {
572  ROS_ERROR_THROTTLE(10.0, "Unknown error starting Hokuyo");
573  urg_.reset();
574  ros::Duration(1.0).sleep();
575  continue; // Return to top of main loop
576  }
577 
578  while (!close_scan_)
579  {
580  // Don't allow external access during grabbing the scan.
581  try
582  {
583  boost::mutex::scoped_lock lock(lidar_mutex_);
584  if (publish_multiecho_)
585  {
586  const sensor_msgs::MultiEchoLaserScanPtr msg(new sensor_msgs::MultiEchoLaserScan());
587  if (urg_->grabScan(msg))
588  {
589  echoes_pub_.publish(msg);
590  echoes_freq_->tick();
591  }
592  else
593  {
594  ROS_WARN_THROTTLE(10.0, "Could not grab multi echo scan.");
595  device_status_ = urg_->getSensorStatus();
596  error_count_++;
597  }
598  }
599  else
600  {
601  const sensor_msgs::LaserScanPtr msg(new sensor_msgs::LaserScan());
602  if (urg_->grabScan(msg))
603  {
604  laser_pub_.publish(msg);
605  laser_freq_->tick();
606  }
607  else
608  {
609  ROS_WARN_THROTTLE(10.0, "Could not grab single echo scan.");
610  device_status_ = urg_->getSensorStatus();
611  error_count_++;
612  }
613  }
614  }
615  catch (...)
616  {
617  ROS_ERROR_THROTTLE(10.0, "Unknown error grabbing Hokuyo scan.");
618  error_count_++;
619  }
620 
621  if (service_yield_)
622  {
623  boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
624  service_yield_ = false;
625  }
626 
627  // Reestablish conneciton if things seem to have gone wrong.
629  {
630  ROS_ERROR_THROTTLE(10.0, "Error count exceeded limit, reconnecting.");
631  urg_.reset();
632  ros::Duration(2.0).sleep();
633 
634  break; // Return to top of main loop
635  }
636  }
637  }
638 }
639 
641 {
642  // Setup initial connection
643  connect();
644 
645  // Stop diagnostics
646  if (!close_diagnostics_)
647  {
648  close_diagnostics_ = true;
649  diagnostics_thread_.join();
650  }
651 
652  if (publish_multiecho_)
653  {
657  }
658  else
659  {
663  }
664 
665  // Now that we are setup, kick off diagnostics.
666  close_diagnostics_ = false;
667  diagnostics_thread_ = boost::thread(boost::bind(&UrgNode::updateDiagnostics, this));
668 
669  // Start scanning now that everything is configured.
670  close_scan_ = false;
671  scan_thread_ = boost::thread(boost::bind(&UrgNode::scanThread, this));
672 }
673 } // namespace urg_node
msg
static LaserPublisher advertiseLaser(ros::NodeHandle &nh, uint32_t queue_size, bool latch=false)
ros::Publisher status_pub_
std::string product_name_
#define ROS_FATAL(...)
bool updateStatus()
Trigger an update of the lidar&#39;s status publish the latest known information about the lidar on latch...
std::string protocol_version_
boost::shared_ptr< urg_node::URGCWrapper > urg_
void summary(unsigned char lvl, const std::string s)
std::string vendor_name_
boost::shared_ptr< bond::Bond > bond_
bool call(const std::string &service_name, MReq &req, MRes &res)
double diagnostics_window_time_
ROSCPP_DECL const std::string & getName()
boost::shared_ptr< diagnostic_updater::HeaderlessTopicDiagnostic > laser_freq_
std::string serial_port_
ServiceServer advertiseService(const std::string &service, bool(T::*srv_func)(MReq &, MRes &), T *obj)
ros::NodeHandle pnh_
boost::thread diagnostics_thread_
#define ROS_WARN(...)
boost::shared_ptr< diagnostic_updater::Updater > diagnostic_updater_
boost::thread scan_thread_
boost::shared_ptr< diagnostic_updater::HeaderlessTopicDiagnostic > echoes_freq_
void summaryf(unsigned char lvl, const char *format,...)
std::string resolve(const std::string &prefix, const std::string &frame_name)
std::string firmware_version_
void publish(const sensor_msgs::MultiEchoLaserScan &msg) const
boost::mutex lidar_mutex_
ROSCPP_DECL void set(const std::string &key, const XmlRpc::XmlRpcValue &v)
std::string device_status_
bool param(const std::string &param_name, T &param_val, const T &default_val) const
void publish(const boost::shared_ptr< M > &message) const
ROSCPP_DECL const std::string & getNamespace()
volatile bool service_yield_
#define ROS_WARN_THROTTLE(period,...)
laser_proc::LaserPublisher echoes_pub_
#define ROS_INFO(...)
#define ROS_ERROR_THROTTLE(period,...)
std::string ip_address_
ROSCPP_DECL bool ok()
std::string device_id_
ROSCPP_DECL bool has(const std::string &key)
Publisher advertise(const std::string &topic, uint32_t queue_size, bool latch=false)
ros::NodeHandle nh_
bool statusCallback(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res)
std::string firmware_date_
boost::shared_ptr< dynamic_reconfigure::Server< urg_node::URGConfig > > srv_
Dynamic reconfigure server.
#define ROS_INFO_STREAM(args)
int min(int a, int b)
diagnostic_updater::FrequencyStatusParam FrequencyStatusParam
ROSCPP_DECL void shutdown()
ros::Publisher laser_pub_
void add(const std::string &key, const T &val)
bool sleep() const
ros::ServiceServer status_service_
ROSCPP_DECL void spinOnce()
void populateDiagnosticsStatus(diagnostic_updater::DiagnosticStatusWrapper &stat)
void run()
Start&#39;s the nodes threads to run the lidar.
#define ROS_ERROR(...)
#define ROS_DEBUG_THROTTLE(period,...)
bool reconfigure_callback(urg_node::URGConfig &config, int level)
ROSCPP_DECL bool waitForService(const std::string &service_name, int32_t timeout)


urg_node
Author(s): Chad Rockey , Mike O'Driscoll
autogenerated on Fri Oct 14 2022 02:41:16