00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030 #include <boost/bind.hpp>
00031 #include <boost/chrono.hpp>
00032 #include <boost/thread.hpp>
00033 #include <boost/thread/lock_guard.hpp>
00034 #include <boost/thread/mutex.hpp>
00035 #include <boost/asio.hpp>
00036
00037 #include <algorithm>
00038 #include <deque>
00039 #include <string>
00040
00041 #include <ros/ros.h>
00042 #include <sensor_msgs/PointCloud.h>
00043 #include <sensor_msgs/PointCloud2.h>
00044 #include <sensor_msgs/point_cloud2_iterator.h>
00045 #include <sensor_msgs/MagneticField.h>
00046 #include <sensor_msgs/Imu.h>
00047 #include <sensor_msgs/point_cloud_conversion.h>
00048
00049 #include <vssp.h>
00050
00051 class Hokuyo3dNode
00052 {
00053 public:
00054 void cbPoint(
00055 const vssp::Header &header,
00056 const vssp::RangeHeader &range_header,
00057 const vssp::RangeIndex &range_index,
00058 const boost::shared_array<uint16_t> &index,
00059 const boost::shared_array<vssp::XYZI> &points,
00060 const boost::posix_time::ptime &time_read)
00061 {
00062 if (timestamp_base_ == ros::Time(0))
00063 return;
00064
00065 if (enable_pc_)
00066 {
00067 if (cloud_.points.size() == 0)
00068 {
00069
00070 cloud_.header.frame_id = frame_id_;
00071 cloud_.header.stamp = timestamp_base_ + ros::Duration(range_header.line_head_timestamp_ms * 0.001);
00072 }
00073
00074 for (int i = 0; i < index[range_index.nspots]; i++)
00075 {
00076 if (points[i].r < range_min_)
00077 {
00078 continue;
00079 }
00080 geometry_msgs::Point32 point;
00081 point.x = points[i].x;
00082 point.y = points[i].y;
00083 point.z = points[i].z;
00084 cloud_.points.push_back(point);
00085 cloud_.channels[0].values.push_back(points[i].i);
00086 }
00087 }
00088 if (enable_pc2_)
00089 {
00090 if (cloud2_.data.size() == 0)
00091 {
00092
00093 cloud2_.header.frame_id = frame_id_;
00094 cloud2_.header.stamp = timestamp_base_ + ros::Duration(range_header.line_head_timestamp_ms * 0.001);
00095 cloud2_.row_step = 0;
00096 cloud2_.width = 0;
00097 }
00098
00099 cloud2_.data.resize((cloud2_.width + index[range_index.nspots]) * cloud2_.point_step);
00100
00101 float *data = reinterpret_cast<float *>(&cloud2_.data[0]);
00102 data += cloud2_.width * cloud2_.point_step / sizeof(float);
00103 for (int i = 0; i < index[range_index.nspots]; i++)
00104 {
00105 if (points[i].r < range_min_)
00106 {
00107 continue;
00108 }
00109 *(data++) = points[i].x;
00110 *(data++) = points[i].y;
00111 *(data++) = points[i].z;
00112 *(data++) = points[i].i;
00113 cloud2_.width++;
00114 }
00115 cloud2_.row_step = cloud2_.width * cloud2_.point_step;
00116 }
00117
00118 if ((cycle_ == CYCLE_FIELD && (range_header.field != field_ || range_header.frame != frame_)) ||
00119 (cycle_ == CYCLE_FRAME && (range_header.frame != frame_)) || (cycle_ == CYCLE_LINE))
00120 {
00121 if (enable_pc_)
00122 {
00123 if (cloud_.header.stamp < cloud_stamp_last_ && !allow_jump_back_)
00124 {
00125 ROS_INFO("Dropping timestamp jump backed cloud");
00126 }
00127 else
00128 {
00129 pub_pc_.publish(cloud_);
00130 }
00131 cloud_stamp_last_ = cloud_.header.stamp;
00132 cloud_.points.clear();
00133 cloud_.channels[0].values.clear();
00134 }
00135 if (enable_pc2_)
00136 {
00137 cloud2_.data.resize(cloud2_.width * cloud2_.point_step);
00138 if (cloud2_.header.stamp < cloud_stamp_last_ && !allow_jump_back_)
00139 {
00140 ROS_INFO("Dropping timestamp jump backed cloud2");
00141 }
00142 else
00143 {
00144 pub_pc2_.publish(cloud2_);
00145 }
00146 cloud_stamp_last_ = cloud2_.header.stamp;
00147 cloud2_.data.clear();
00148 }
00149 if (range_header.frame != frame_)
00150 ping();
00151 frame_ = range_header.frame;
00152 field_ = range_header.field;
00153 line_ = range_header.line;
00154 }
00155 }
00156 void cbError(
00157 const vssp::Header &header,
00158 const std::string &message,
00159 const boost::posix_time::ptime &time_read)
00160 {
00161 ROS_ERROR("%s", message.c_str());
00162 }
00163 void cbPing(
00164 const vssp::Header &header,
00165 const boost::posix_time::ptime &time_read)
00166 {
00167 const ros::Time now = ros::Time::fromBoost(time_read);
00168 const ros::Duration delay =
00169 ((now - time_ping_) - ros::Duration(header.send_time_ms * 0.001 - header.received_time_ms * 0.001)) * 0.5;
00170 const ros::Time base = time_ping_ + delay - ros::Duration(header.received_time_ms * 0.001);
00171
00172 timestamp_base_buffer_.push_back(base);
00173 if (timestamp_base_buffer_.size() > 5)
00174 timestamp_base_buffer_.pop_front();
00175
00176 auto sorted_timstamp_base = timestamp_base_buffer_;
00177 std::sort(sorted_timstamp_base.begin(), sorted_timstamp_base.end());
00178
00179 if (timestamp_base_ == ros::Time(0))
00180 timestamp_base_ = sorted_timstamp_base[sorted_timstamp_base.size() / 2];
00181 else
00182 timestamp_base_ += (sorted_timstamp_base[sorted_timstamp_base.size() / 2] - timestamp_base_) * 0.1;
00183
00184 ROS_DEBUG("timestamp_base: %lf", timestamp_base_.toSec());
00185 }
00186 void cbAux(
00187 const vssp::Header &header,
00188 const vssp::AuxHeader &aux_header,
00189 const boost::shared_array<vssp::Aux> &auxs,
00190 const boost::posix_time::ptime &time_read)
00191 {
00192 if (timestamp_base_ == ros::Time(0))
00193 return;
00194 ros::Time stamp = timestamp_base_ + ros::Duration(aux_header.timestamp_ms * 0.001);
00195
00196 if ((aux_header.data_bitfield & (vssp::AX_MASK_ANGVEL | vssp::AX_MASK_LINACC)) ==
00197 (vssp::AX_MASK_ANGVEL | vssp::AX_MASK_LINACC))
00198 {
00199 imu_.header.frame_id = imu_frame_id_;
00200 imu_.header.stamp = stamp;
00201 for (int i = 0; i < aux_header.data_count; i++)
00202 {
00203 imu_.orientation_covariance[0] = -1.0;
00204 imu_.angular_velocity.x = auxs[i].ang_vel.x;
00205 imu_.angular_velocity.y = auxs[i].ang_vel.y;
00206 imu_.angular_velocity.z = auxs[i].ang_vel.z;
00207 imu_.linear_acceleration.x = auxs[i].lin_acc.x;
00208 imu_.linear_acceleration.y = auxs[i].lin_acc.y;
00209 imu_.linear_acceleration.z = auxs[i].lin_acc.z;
00210 if (imu_stamp_last_ > imu_.header.stamp && !allow_jump_back_)
00211 {
00212 ROS_INFO("Dropping timestamp jump backed imu");
00213 }
00214 else
00215 {
00216 pub_imu_.publish(imu_);
00217 }
00218 imu_stamp_last_ = imu_.header.stamp;
00219 imu_.header.stamp += ros::Duration(aux_header.data_ms * 0.001);
00220 }
00221 }
00222 if ((aux_header.data_bitfield & vssp::AX_MASK_MAG) == vssp::AX_MASK_MAG)
00223 {
00224 mag_.header.frame_id = mag_frame_id_;
00225 mag_.header.stamp = stamp;
00226 for (int i = 0; i < aux_header.data_count; i++)
00227 {
00228 mag_.magnetic_field.x = auxs[i].mag.x;
00229 mag_.magnetic_field.y = auxs[i].mag.y;
00230 mag_.magnetic_field.z = auxs[i].mag.z;
00231 if (mag_stamp_last_ > imu_.header.stamp && !allow_jump_back_)
00232 {
00233 ROS_INFO("Dropping timestamp jump backed mag");
00234 }
00235 else
00236 {
00237 pub_mag_.publish(mag_);
00238 }
00239 mag_stamp_last_ = imu_.header.stamp;
00240 mag_.header.stamp += ros::Duration(aux_header.data_ms * 0.001);
00241 }
00242 }
00243 }
00244 void cbConnect(bool success)
00245 {
00246 if (success)
00247 {
00248 ROS_INFO("Connection established");
00249 ping();
00250 if (set_auto_reset_)
00251 driver_.setAutoReset(auto_reset_);
00252 driver_.setHorizontalInterlace(horizontal_interlace_);
00253 driver_.requestHorizontalTable();
00254 driver_.setVerticalInterlace(vertical_interlace_);
00255 driver_.requestVerticalTable(vertical_interlace_);
00256 driver_.requestData(true, true);
00257 driver_.requestAuxData();
00258 driver_.receivePackets();
00259 ROS_INFO("Communication started");
00260 }
00261 else
00262 {
00263 ROS_ERROR("Connection failed");
00264 }
00265 }
00266 Hokuyo3dNode()
00267 : pnh_("~")
00268 , timestamp_base_(0)
00269 , timer_(io_, boost::posix_time::milliseconds(500))
00270 {
00271 if (pnh_.hasParam("horizontal_interlace") || !pnh_.hasParam("interlace"))
00272 {
00273 pnh_.param("horizontal_interlace", horizontal_interlace_, 4);
00274 }
00275 else if (pnh_.hasParam("interlace"))
00276 {
00277 ROS_WARN("'interlace' parameter is deprecated. Use horizontal_interlace instead.");
00278 pnh_.param("interlace", horizontal_interlace_, 4);
00279 }
00280 pnh_.param("vertical_interlace", vertical_interlace_, 1);
00281 pnh_.param("ip", ip_, std::string("192.168.0.10"));
00282 pnh_.param("port", port_, 10940);
00283 pnh_.param("frame_id", frame_id_, std::string("hokuyo3d"));
00284 pnh_.param("imu_frame_id", imu_frame_id_, frame_id_ + "_imu");
00285 pnh_.param("mag_frame_id", mag_frame_id_, frame_id_ + "_mag");
00286 pnh_.param("range_min", range_min_, 0.0);
00287 set_auto_reset_ = pnh_.hasParam("auto_reset");
00288 pnh_.param("auto_reset", auto_reset_, false);
00289
00290 pnh_.param("allow_jump_back", allow_jump_back_, false);
00291
00292 std::string output_cycle;
00293 pnh_.param("output_cycle", output_cycle, std::string("field"));
00294
00295 if (output_cycle.compare("frame") == 0)
00296 cycle_ = CYCLE_FRAME;
00297 else if (output_cycle.compare("field") == 0)
00298 cycle_ = CYCLE_FIELD;
00299 else if (output_cycle.compare("line") == 0)
00300 cycle_ = CYCLE_LINE;
00301 else
00302 {
00303 ROS_ERROR("Unknown output_cycle value %s", output_cycle.c_str());
00304 ros::shutdown();
00305 }
00306
00307 driver_.setTimeout(2.0);
00308 ROS_INFO("Connecting to %s", ip_.c_str());
00309 driver_.registerCallback(boost::bind(&Hokuyo3dNode::cbPoint, this, _1, _2, _3, _4, _5, _6));
00310 driver_.registerAuxCallback(boost::bind(&Hokuyo3dNode::cbAux, this, _1, _2, _3, _4));
00311 driver_.registerPingCallback(boost::bind(&Hokuyo3dNode::cbPing, this, _1, _2));
00312 driver_.registerErrorCallback(boost::bind(&Hokuyo3dNode::cbError, this, _1, _2, _3));
00313 field_ = 0;
00314 frame_ = 0;
00315 line_ = 0;
00316
00317 sensor_msgs::ChannelFloat32 channel;
00318 channel.name = std::string("intensity");
00319 cloud_.channels.push_back(channel);
00320
00321 cloud2_.height = 1;
00322 cloud2_.is_bigendian = false;
00323 cloud2_.is_dense = false;
00324 sensor_msgs::PointCloud2Modifier pc2_modifier(cloud2_);
00325 pc2_modifier.setPointCloud2Fields(4, "x", 1, sensor_msgs::PointField::FLOAT32, "y", 1,
00326 sensor_msgs::PointField::FLOAT32, "z", 1, sensor_msgs::PointField::FLOAT32,
00327 "intensity", 1, sensor_msgs::PointField::FLOAT32);
00328
00329 pub_imu_ = pnh_.advertise<sensor_msgs::Imu>("imu", 5);
00330 pub_mag_ = pnh_.advertise<sensor_msgs::MagneticField>("mag", 5);
00331
00332 enable_pc_ = enable_pc2_ = false;
00333 ros::SubscriberStatusCallback cb_con = boost::bind(&Hokuyo3dNode::cbSubscriber, this);
00334
00335 boost::lock_guard<boost::mutex> lock(connect_mutex_);
00336 pub_pc_ = pnh_.advertise<sensor_msgs::PointCloud>("hokuyo_cloud", 5, cb_con, cb_con);
00337 pub_pc2_ = pnh_.advertise<sensor_msgs::PointCloud2>("hokuyo_cloud2", 5, cb_con, cb_con);
00338
00339
00340 driver_.connect(ip_.c_str(), port_, boost::bind(&Hokuyo3dNode::cbConnect, this, _1));
00341 }
00342 ~Hokuyo3dNode()
00343 {
00344 driver_.requestAuxData(false);
00345 driver_.requestData(true, false);
00346 driver_.requestData(false, false);
00347 driver_.poll();
00348 ROS_INFO("Communication stoped");
00349 }
00350 void cbSubscriber()
00351 {
00352 boost::lock_guard<boost::mutex> lock(connect_mutex_);
00353 if (pub_pc_.getNumSubscribers() > 0)
00354 {
00355 enable_pc_ = true;
00356 ROS_DEBUG("PointCloud output enabled");
00357 }
00358 else
00359 {
00360 enable_pc_ = false;
00361 ROS_DEBUG("PointCloud output disabled");
00362 }
00363 if (pub_pc2_.getNumSubscribers() > 0)
00364 {
00365 enable_pc2_ = true;
00366 ROS_DEBUG("PointCloud2 output enabled");
00367 }
00368 else
00369 {
00370 enable_pc2_ = false;
00371 ROS_DEBUG("PointCloud2 output disabled");
00372 }
00373 }
00374 bool poll()
00375 {
00376 if (driver_.poll())
00377 {
00378 return true;
00379 }
00380 ROS_INFO("Connection closed");
00381 return false;
00382 }
00383 void cbTimer(const boost::system::error_code& error)
00384 {
00385 if (error)
00386 return;
00387
00388 if (!ros::ok())
00389 {
00390 driver_.stop();
00391 }
00392 else
00393 {
00394 timer_.expires_at(
00395 timer_.expires_at() +
00396 boost::posix_time::milliseconds(500));
00397 timer_.async_wait(boost::bind(&Hokuyo3dNode::cbTimer, this, _1));
00398 }
00399 }
00400 void spin()
00401 {
00402 timer_.async_wait(boost::bind(&Hokuyo3dNode::cbTimer, this, _1));
00403 boost::thread thread(
00404 boost::bind(&boost::asio::io_service::run, &io_));
00405
00406 ros::AsyncSpinner spinner(1);
00407 spinner.start();
00408 driver_.spin();
00409 spinner.stop();
00410 timer_.cancel();
00411 ROS_INFO("Connection closed");
00412 }
00413 void ping()
00414 {
00415 driver_.requestPing();
00416 time_ping_ = ros::Time::now();
00417 }
00418
00419 protected:
00420 ros::NodeHandle pnh_;
00421 ros::Publisher pub_pc_;
00422 ros::Publisher pub_pc2_;
00423 ros::Publisher pub_imu_;
00424 ros::Publisher pub_mag_;
00425 vssp::VsspDriver driver_;
00426 sensor_msgs::PointCloud cloud_;
00427 sensor_msgs::PointCloud2 cloud2_;
00428 sensor_msgs::Imu imu_;
00429 sensor_msgs::MagneticField mag_;
00430
00431 bool enable_pc_;
00432 bool enable_pc2_;
00433 bool allow_jump_back_;
00434 boost::mutex connect_mutex_;
00435
00436 ros::Time time_ping_;
00437 ros::Time timestamp_base_;
00438 std::deque<ros::Time> timestamp_base_buffer_;
00439 ros::Time imu_stamp_last_;
00440 ros::Time mag_stamp_last_;
00441 ros::Time cloud_stamp_last_;
00442
00443 boost::asio::io_service io_;
00444 boost::asio::deadline_timer timer_;
00445
00446 int field_;
00447 int frame_;
00448 int line_;
00449
00450 enum PublishCycle
00451 {
00452 CYCLE_FIELD,
00453 CYCLE_FRAME,
00454 CYCLE_LINE
00455 };
00456 PublishCycle cycle_;
00457 std::string ip_;
00458 int port_;
00459 int horizontal_interlace_;
00460 int vertical_interlace_;
00461 double range_min_;
00462 std::string frame_id_;
00463 std::string imu_frame_id_;
00464 std::string mag_frame_id_;
00465 bool auto_reset_;
00466 bool set_auto_reset_;
00467 };
00468
00469 int main(int argc, char **argv)
00470 {
00471 ros::init(argc, argv, "hokuyo3d");
00472 Hokuyo3dNode node;
00473
00474 node.spin();
00475
00476 return 1;
00477 }