diff --git a/CMakeLists.txt b/CMakeLists.txt index c8dedc7..153b6a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,18 +5,21 @@ project(lms1xx) find_package(console_bridge REQUIRED) include_directories(include ${console_bridge_INCLUDE_DIRS}) add_library(LMS1xx src/LMS1xx.cpp) +set_property(TARGET LMS1xx PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(LMS1xx ${console_bridge_LIBRARIES}) # Regular catkin package follows. -find_package(catkin REQUIRED COMPONENTS roscpp sensor_msgs) -catkin_package(CATKIN_DEPENDS roscpp) +find_package(catkin REQUIRED COMPONENTS nodelet roscpp sensor_msgs) +catkin_package(CATKIN_DEPENDS nodelet roscpp) include_directories(include ${catkin_INCLUDE_DIRS}) add_executable(LMS1xx_node src/LMS1xx_node.cpp) target_link_libraries(LMS1xx_node LMS1xx ${catkin_LIBRARIES}) +add_library(LMS1xx_nodelet src/LMS1xx_nodelet.cpp) +target_link_libraries(LMS1xx_nodelet LMS1xx ${catkin_LIBRARIES}) -install(TARGETS LMS1xx LMS1xx_node +install(TARGETS LMS1xx LMS1xx_node LMS1xx_nodelet ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} @@ -25,6 +28,9 @@ install(TARGETS LMS1xx LMS1xx_node install(DIRECTORY meshes launch urdf DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) +install(FILES nodelets.xml + DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}) + install(PROGRAMS scripts/find_sick scripts/set_sick_ip DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}) diff --git a/README.md b/README.md index ec2c167..7d07ac6 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,10 @@ LMS1xx [![Build Status](https://travis-ci.org/clearpathrobotics/LMS1xx.svg?branc ====== ROS driver for the SICK LMS1xx family of laser scanners. Originally from [RCPRG](https://github.com/RCPRG-ros-pkg/RCPRG_laser_drivers). + + +Docs +==== + +* [Technical Information](https://cdn.sick.com/media/docs/7/27/927/Technical_information_Telegram_Listing_Ranging_sensors_LMS1xx_LMS5xx_TiM5xx_TiM7xx_LMS1000_MRS1000_MRS6000_NAV310_LD_OEM15xx_LD_LRS36xx_LMS4000_en_IM0045927.PDF) +* [Operating Instructions](https://cdn.sick.com/media/docs/1/31/331/Operating_instructions_LMS1xx_Laser_Measurement_Sensors_en_IM0031331.PDF) \ No newline at end of file diff --git a/launch/LMS1xx.launch b/launch/LMS1xx.launch index ea81081..3c6b51e 100644 --- a/launch/LMS1xx.launch +++ b/launch/LMS1xx.launch @@ -1,8 +1,23 @@ - + + + + + + + + + + + + + + + + diff --git a/nodelets.xml b/nodelets.xml new file mode 100644 index 0000000..49b504d --- /dev/null +++ b/nodelets.xml @@ -0,0 +1,7 @@ + + + + Nodelet version of LMS1xx driver. + + + \ No newline at end of file diff --git a/package.xml b/package.xml index d68a190..748477b 100644 --- a/package.xml +++ b/package.xml @@ -11,6 +11,7 @@ LGPL catkin + nodelet rosconsole_bridge roscpp roscpp_serialization @@ -19,4 +20,8 @@ roslaunch roslint rosunit + + + + diff --git a/src/LMS1xx.cpp b/src/LMS1xx.cpp index f61d45d..ea7cf41 100644 --- a/src/LMS1xx.cpp +++ b/src/LMS1xx.cpp @@ -363,6 +363,27 @@ void LMS1xx::parseScanData(char* buffer, scanData* data) data->rssi_len2 = NumberData; } + + /* + * TODO: there are range/rssi values with special meaning! + * + * LMDscandata - reserved valuesValid distance measurement values are values starting from 16d upwards; + * everything below has the following meaning: + * + * DIST RSSI Description + * 0d 0h no meas value detected; means that in the angle, there was no valid measurement + * value. Probably the object to measure was out of the range of the or the object + * was reflecting too less light back (black objects) + * 1d FFFFh (16Bit output) + * FFh(8Bit output) + * dazzled, blinded + * 2d 0h implausible measurement values + * 3d 0h value was set to invalid by a filter (Echo Filter,Particle Filterin old firmware) + * 4–15d 0h reserved, at the moment not given out, if there occurs a value in that range any + * way perform a Softwareupdate + * ≥16d >0h valid measurement val + */ + for (int i = 0; i < NumberData; i++) { int dat; diff --git a/src/LMS1xx_node.cpp b/src/LMS1xx_node.cpp index 03fbf28..3a00b40 100644 --- a/src/LMS1xx_node.cpp +++ b/src/LMS1xx_node.cpp @@ -38,22 +38,28 @@ int main(int argc, char **argv) scanCfg cfg; scanOutputRange outputRange; scanDataCfg dataCfg; - sensor_msgs::LaserScan scan_msg; + sensor_msgs::LaserScan scan_msg, scan2_msg; // parameters std::string host; std::string frame_id; bool inf_range; + bool publish_2nd_pulse; int port; + float range_min, range_max; ros::init(argc, argv, "lms1xx"); ros::NodeHandle nh; ros::NodeHandle n("~"); ros::Publisher scan_pub = nh.advertise("scan", 1); + ros::Publisher scan2_pub = nh.advertise("scan_2nd_pulse", 1); n.param("host", host, "192.168.1.2"); n.param("frame_id", frame_id, "laser"); n.param("publish_min_range_as_inf", inf_range, false); + n.param("publish_2nd_pulse", publish_2nd_pulse, false); + n.param("range_min", range_min, 0.01); + n.param("range_max", range_max, 20.0); n.param("port", port, 2111); while (ros::ok()) @@ -87,14 +93,16 @@ int main(int argc, char **argv) ROS_DEBUG("Laser output range:angleResolution %d, startAngle %d, stopAngle %d", outputRange.angleResolution, outputRange.startAngle, outputRange.stopAngle); - scan_msg.header.frame_id = frame_id; - scan_msg.range_min = 0.01; - scan_msg.range_max = 20.0; - scan_msg.scan_time = 100.0 / cfg.scaningFrequency; - scan_msg.angle_increment = static_cast(outputRange.angleResolution / 10000.0 * DEG2RAD); - scan_msg.angle_min = static_cast(outputRange.startAngle / 10000.0 * DEG2RAD - M_PI / 2); - scan_msg.angle_max = static_cast(outputRange.stopAngle / 10000.0 * DEG2RAD - M_PI / 2); - + scan_msg.header.frame_id = scan2_msg.header.frame_id = frame_id; + scan_msg.range_min = scan2_msg.range_min = range_min; + scan_msg.range_max = scan2_msg.range_max = range_max; + scan_msg.scan_time = scan2_msg.scan_time = 100.0 / cfg.scaningFrequency; + scan_msg.angle_increment = scan2_msg.angle_increment = + static_cast(outputRange.angleResolution / 10000.0 * DEG2RAD); + scan_msg.angle_min = scan2_msg.angle_min = static_cast( + outputRange.startAngle / 10000.0 * DEG2RAD - M_PI / 2); + scan_msg.angle_max = scan2_msg.angle_max = static_cast( + outputRange.stopAngle / 10000.0 * DEG2RAD - M_PI / 2); ROS_DEBUG_STREAM("Device resolution is " << (double)outputRange.angleResolution / 10000.0 << " degrees."); ROS_DEBUG_STREAM("Device frequency is " << (double)cfg.scaningFrequency / 100.0 << " Hz"); @@ -107,15 +115,19 @@ int main(int argc, char **argv) } scan_msg.ranges.resize(num_values); scan_msg.intensities.resize(num_values); + if (publish_2nd_pulse) { + scan2_msg.ranges.resize(num_values); + scan2_msg.intensities.resize(num_values); + } - scan_msg.time_increment = + scan_msg.time_increment = scan2_msg.time_increment = (outputRange.angleResolution / 10000.0) / 360.0 / (cfg.scaningFrequency / 100.0); ROS_DEBUG_STREAM("Time increment is " << static_cast(scan_msg.time_increment * 1000000) << " microseconds"); - dataCfg.outputChannel = 1; + dataCfg.outputChannel = publish_2nd_pulse ? 3 : 1; dataCfg.remission = true; dataCfg.resolution = 1; dataCfg.encoder = 0; @@ -173,8 +185,9 @@ int main(int argc, char **argv) { ros::Time start = ros::Time::now(); - scan_msg.header.stamp = start; + scan_msg.header.stamp = scan2_msg.header.stamp = start; ++scan_msg.header.seq; + ++scan2_msg.header.seq; scanData data; ROS_DEBUG("Reading scan data."); @@ -182,7 +195,7 @@ int main(int argc, char **argv) { for (int i = 0; i < data.dist_len1; i++) { - float range_data = data.dist1[i] * 0.001; + float range_data = data.dist1[i] * 0.001f; if (inf_range && range_data < scan_msg.range_min) { @@ -201,6 +214,25 @@ int main(int argc, char **argv) ROS_DEBUG("Publishing scan data."); scan_pub.publish(scan_msg); + + if (publish_2nd_pulse) { + for (int i = 0; i < data.dist_len2; i++) { + float range_data = data.dist2[i] * 0.001f; + + if (inf_range && range_data < scan2_msg.range_min) { + scan2_msg.ranges[i] = std::numeric_limits::infinity(); + } else { + scan2_msg.ranges[i] = range_data; + } + } + + for (int i = 0; i < data.rssi_len2; i++) { + scan2_msg.intensities[i] = data.rssi2[i]; + } + + ROS_DEBUG("Publishing second pulse scan data."); + scan2_pub.publish(scan2_msg); + } } else { diff --git a/src/LMS1xx_nodelet.cpp b/src/LMS1xx_nodelet.cpp new file mode 100644 index 0000000..6ac241b --- /dev/null +++ b/src/LMS1xx_nodelet.cpp @@ -0,0 +1,263 @@ +#include +#include + +#include +#include +#include +#include + +#include + +#define DEG2RAD M_PI/180.0 + +namespace lms1xx { + +class LMS1xxNodelet : public nodelet::Nodelet { + +public: + virtual ~LMS1xxNodelet(); + +protected: + void onInit() override; + void loop(); + + std::thread thread; + + LMS1xx laser; + scanCfg cfg; + scanOutputRange outputRange; + scanDataCfg dataCfg; + ros::Publisher scan_pub, scan2_pub; + + // parameters + std::string host; + std::string frame_id; + bool inf_range; + bool publish_2nd_pulse; + int port; + float range_min, range_max; + + ros::NodeHandle nh, n; + + volatile bool isShuttingDown = false; + + bool ok() const { + return !isShuttingDown && ros::ok(); + } +}; + + +void LMS1xxNodelet::onInit() { + nh = getNodeHandle(); + n = getPrivateNodeHandle(); + + n.param("host", host, "192.168.1.2"); + n.param("frame_id", frame_id, "laser"); + n.param("publish_min_range_as_inf", inf_range, false); + n.param("publish_2nd_pulse", publish_2nd_pulse, false); + n.param("range_min", range_min, 0.01); + n.param("range_max", range_max, 20.0); + n.param("port", port, 2111); + + if (publish_2nd_pulse) + NODELET_DEBUG("Reading and publishing also second laser reflections."); + + scan_pub = nh.advertise("scan", 1); + if (publish_2nd_pulse) + scan2_pub = nh.advertise("scan_2nd_pulse", 1); + + thread = std::thread(&LMS1xxNodelet::loop, this); +} + +void LMS1xxNodelet::loop() { + while (this->ok()) { + NODELET_INFO_STREAM("Connecting to laser at " << host); + laser.connect(host, port); + if (!laser.isConnected()) { + NODELET_WARN("Unable to connect, retrying."); + ros::WallDuration(1).sleep(); + continue; + } + + NODELET_DEBUG("Logging in to laser."); + laser.login(); + cfg = laser.getScanCfg(); + outputRange = laser.getScanOutputRange(); + + if (cfg.scaningFrequency != 5000) { + laser.disconnect(); + NODELET_WARN("Unable to get laser output range. Retrying."); + ros::WallDuration(1).sleep(); + continue; + } + + NODELET_INFO("Connected to laser."); + + NODELET_DEBUG("Laser configuration: scaningFrequency %d, angleResolution %d, startAngle %d, stopAngle %d", + cfg.scaningFrequency, cfg.angleResolution, cfg.startAngle, + cfg.stopAngle); + NODELET_DEBUG( + "Laser output range:angleResolution %d, startAngle %d, stopAngle %d", + outputRange.angleResolution, outputRange.startAngle, + outputRange.stopAngle); + + sensor_msgs::LaserScanPtr scan_msg(new sensor_msgs::LaserScan()); + sensor_msgs::LaserScanPtr scan2_msg(new sensor_msgs::LaserScan()); + + scan_msg->header.frame_id = scan2_msg->header.frame_id = frame_id; + scan_msg->range_min = scan2_msg->range_min = range_min; + scan_msg->range_max = scan2_msg->range_max = range_max; + scan_msg->scan_time = scan2_msg->scan_time = 100.0 / cfg.scaningFrequency; + scan_msg->angle_increment = scan2_msg->angle_increment = + static_cast(outputRange.angleResolution / 10000.0 * DEG2RAD); + scan_msg->angle_min = scan2_msg->angle_min = static_cast( + outputRange.startAngle / 10000.0 * DEG2RAD - M_PI / 2); + scan_msg->angle_max = scan2_msg->angle_max = static_cast( + outputRange.stopAngle / 10000.0 * DEG2RAD - M_PI / 2); + + NODELET_DEBUG_STREAM("Device resolution is " + << (double)outputRange.angleResolution / 10000.0 + << " degrees."); + NODELET_DEBUG_STREAM("Device frequency is " + << (double)cfg.scaningFrequency / 100.0 << " Hz"); + + int angle_range = outputRange.stopAngle - outputRange.startAngle; + int num_values = angle_range / outputRange.angleResolution; + if (angle_range % outputRange.angleResolution == 0) { + // Include endpoint + ++num_values; + } + scan_msg->ranges.resize(num_values); + scan_msg->intensities.resize(num_values); + if (publish_2nd_pulse) { + scan2_msg->ranges.resize(num_values); + scan2_msg->intensities.resize(num_values); + } + + scan_msg->time_increment = scan2_msg->time_increment = + (outputRange.angleResolution / 10000.0) / 360.0 / + (cfg.scaningFrequency / 100.0); + + NODELET_DEBUG_STREAM("Time increment is " + << static_cast(scan_msg->time_increment * 1000000) + << " microseconds"); + + dataCfg.outputChannel = publish_2nd_pulse ? 3 : 1; + dataCfg.remission = true; + dataCfg.resolution = 1; + dataCfg.encoder = 0; + dataCfg.position = false; + dataCfg.deviceName = false; + dataCfg.outputInterval = 1; + + NODELET_DEBUG("Setting scan data configuration."); + laser.setScanDataCfg(dataCfg); + + NODELET_DEBUG("Starting measurements."); + laser.startMeas(); + + NODELET_DEBUG("Waiting for ready status."); + ros::Time ready_status_timeout = ros::Time::now() + ros::Duration(5); + + // while(1) + // { + status_t stat = laser.queryStatus(); + ros::WallDuration(1.0).sleep(); + if (stat != ready_for_measurement) { + NODELET_WARN("Laser not ready. Retrying initialization."); + laser.disconnect(); + ros::WallDuration(1).sleep(); + continue; + } + /*if (stat == ready_for_measurement) + { + NODELET_DEBUG("Ready status achieved."); + break; + } + + if (ros::Time::now() > ready_status_timeout) + { + NODELET_WARN("Timed out waiting for ready status. Trying again."); + laser.disconnect(); + continue; + } + + if (!ros::ok()) + { + laser.disconnect(); + return 1; + } + }*/ + + NODELET_DEBUG("Starting device."); + laser.startDevice(); // Log out to properly re-enable system after config + + NODELET_DEBUG("Commanding continuous measurements."); + laser.scanContinous(1); + + while (this->ok()) { + ros::Time start = ros::Time::now(); + + scan_msg->header.stamp = scan2_msg->header.stamp = start; + ++scan_msg->header.seq; + ++scan2_msg->header.seq; + + scanData data; + NODELET_DEBUG("Reading scan data."); + if (laser.getScanData(&data)) { + for (int i = 0; i < data.dist_len1; i++) { + float range_data = data.dist1[i] * 0.001f; + + if (inf_range && range_data < scan_msg->range_min) { + scan_msg->ranges[i] = std::numeric_limits::infinity(); + } else { + scan_msg->ranges[i] = range_data; + } + } + + for (int i = 0; i < data.rssi_len1; i++) { + scan_msg->intensities[i] = data.rssi1[i]; + } + + NODELET_DEBUG("Publishing scan data."); + scan_pub.publish(scan_msg); + + if (publish_2nd_pulse) { + for (int i = 0; i < data.dist_len2; i++) { + float range_data = data.dist2[i] * 0.001f; + + if (inf_range && range_data < scan2_msg->range_min) { + scan2_msg->ranges[i] = std::numeric_limits::infinity(); + } else { + scan2_msg->ranges[i] = range_data; + } + } + + for (int i = 0; i < data.rssi_len2; i++) { + scan2_msg->intensities[i] = data.rssi2[i]; + } + + NODELET_DEBUG("Publishing second pulse scan data."); + scan2_pub.publish(scan2_msg); + } + } else { + NODELET_ERROR( + "Laser timed out on delivering scan, attempting to reinitialize."); + break; + } + } + + laser.scanContinous(0); + laser.stopMeas(); + laser.disconnect(); + } +} + +LMS1xxNodelet::~LMS1xxNodelet() { + this->isShuttingDown; + this->thread.join(); +} + +} + +PLUGINLIB_EXPORT_CLASS(lms1xx::LMS1xxNodelet, nodelet::Nodelet)