diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ecfb9fb4..21794558 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,6 +7,10 @@ Changelog ouster_ros(1) ------------- * breaking: publish PCL point clouds destaggered. +* introduced a new launch file parameter ``ptp_utc_tai_offset`` which represent offset in seconds + to be applied to all ROS messages the driver generates when ``TIME_FROM_PTP_1588`` timestamp mode + is used. +* fix: destagger columns timestamp when generating destaggered point clouds. ouster_ros v0.10.0 diff --git a/Dockerfile b/Dockerfile index 34811a05..f7fed546 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,8 +29,8 @@ RUN set -xe \ && groupadd -o -g ${BUILD_GID} build \ && useradd -o -u ${BUILD_UID} -d ${BUILD_HOME} -rm -s /bin/bash -g build build -# Install build dependencies using rosdep -COPY --chown=build:build package.xml $OUSTER_ROS_PATH/package.xml +# Set up build environment +COPY --chown=build:build . $OUSTER_ROS_PATH RUN set -xe \ && apt-get update \ @@ -38,9 +38,6 @@ RUN set -xe \ && rosdep update --rosdistro=$ROS_DISTRO \ && rosdep install -y --from-paths $OUSTER_ROS_PATH -# Set up build environment -COPY --chown=build:build . $OUSTER_ROS_PATH - USER build:build WORKDIR ${BUILD_HOME} diff --git a/README.md b/README.md index 042dd3ef..9c97ffdc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [ROS1 (melodic/noetic)](https://github.com/ouster-lidar/ouster-ros/tree/master) | [ROS2 (rolling/humble/iron)](https://github.com/ouster-lidar/ouster-ros/tree/ros2) | -[ROS2 (foxy)](https://github.com/ouster-lidar/ouster-ros/tree/ros2-foxy) +[ROS2 (galactic/foxy)](https://github.com/ouster-lidar/ouster-ros/tree/ros2-foxy)

@@ -10,7 +10,7 @@ |:-----------:|:------:| | ROS1 (melodic/noetic) | [![melodic/noetic](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml/badge.svg?branch=master)](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml) | ROS2 (rolling/humble/iron) | [![rolling/humble/iron](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml/badge.svg?branch=ros2)](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml) -| ROS2 (foxy) | [![foxy](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml/badge.svg?branch=ros2-foxy)](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml) +| ROS2 (galactic/foxy) | [![galactic/foxy](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml/badge.svg?branch=ros2-foxy)](https://github.com/ouster-lidar/ouster-ros/actions/workflows/docker-image.yml) - [Overview](#overview) - [Requirements](#requirements) diff --git a/include/ouster_ros/os_ros.h b/include/ouster_ros/os_ros.h index 8868a9c9..1eb70146 100644 --- a/include/ouster_ros/os_ros.h +++ b/include/ouster_ros/os_ros.h @@ -206,7 +206,20 @@ inline ouster::img_t get_or_fill_zero(sensor::ChanField field, return result; } -ros::Time ts_to_ros_time(uint64_t ts); +/** + * simple utility function that ensures we don't wrap around uint64_t due + * to a negative value being bigger than ts value in absolute terms. + * @remark method does not check upper boundary + */ +inline uint64_t ts_safe_offset_add(uint64_t ts, int64_t offset) { + return offset < 0 && ts < static_cast(std::abs(offset)) ? 0 : ts + offset; +} + +inline ros::Time ts_to_ros_time(uint64_t ts) { + ros::Time t; + t.fromNSec(ts); + return t; +} } // namespace impl diff --git a/launch/common.launch b/launch/common.launch index 70c3bc9b..81af6c23 100644 --- a/launch/common.launch +++ b/launch/common.launch @@ -17,8 +17,9 @@ would set lidar_frame to be the frame used when publishing these messages."/> - + + + @@ -47,6 +48,7 @@ value="$(arg dynamic_transforms_broadcast_rate)"/> + diff --git a/launch/driver.launch b/launch/driver.launch index c3b4f633..7ecc5a82 100644 --- a/launch/driver.launch +++ b/launch/driver.launch @@ -25,6 +25,8 @@ TIME_FROM_PTP_1588, TIME_FROM_ROS_TIME }"/> + @@ -71,6 +73,8 @@ + diff --git a/launch/record.launch b/launch/record.launch index df445780..cfa39c3e 100644 --- a/launch/record.launch +++ b/launch/record.launch @@ -25,6 +25,8 @@ TIME_FROM_PTP_1588, TIME_FROM_ROS_TIME }"/> + @@ -89,6 +91,7 @@ + + @@ -64,6 +66,7 @@ + + + @@ -92,6 +95,7 @@ + diff --git a/launch/sensor_mtp.launch b/launch/sensor_mtp.launch index 39545c15..5f481009 100644 --- a/launch/sensor_mtp.launch +++ b/launch/sensor_mtp.launch @@ -29,6 +29,9 @@ TIME_FROM_PTP_1588, TIME_FROM_ROS_TIME }"/> + + @@ -98,6 +101,7 @@ + diff --git a/package.xml b/package.xml index b4fea8ca..32ed19b4 100644 --- a/package.xml +++ b/package.xml @@ -1,7 +1,7 @@ ouster_ros - 0.9.1 + 0.9.2 Ouster ROS driver ouster developers BSD diff --git a/src/imu_packet_handler.h b/src/imu_packet_handler.h index ffd89113..b8349ba9 100644 --- a/src/imu_packet_handler.h +++ b/src/imu_packet_handler.h @@ -24,20 +24,31 @@ class ImuPacketHandler { public: static HandlerType create_handler(const ouster::sensor::sensor_info& info, const std::string& frame, - bool use_ros_time) { + const std::string& timestamp_mode, + int64_t ptp_utc_tai_offset) { const auto& pf = ouster::sensor::get_format(info); using Timestamper = std::function; - // clang-format off - auto timestamper = use_ros_time ? - Timestamper{[](const uint8_t* /*imu_buf*/) { - return ros::Time::now(); }} : - Timestamper{[pf](const uint8_t* imu_buf) { - return impl::ts_to_ros_time(pf.imu_gyro_ts(imu_buf)); }}; - // clang-format on + Timestamper timestamper; + if (timestamp_mode == "TIME_FROM_ROS_TIME") { + timestamper = Timestamper{ + [](const uint8_t* /*imu_buf*/) { return ros::Time::now(); }}; + } else if (timestamp_mode == "TIME_FROM_PTP_1588") { + timestamper = + Timestamper{[pf, ptp_utc_tai_offset](const uint8_t* imu_buf) { + uint64_t ts = pf.imu_gyro_ts(imu_buf); + ts = impl::ts_safe_offset_add(ts, ptp_utc_tai_offset); + return impl::ts_to_ros_time(ts); + }}; + } else { + timestamper = Timestamper{[pf](const uint8_t* imu_buf) { + return impl::ts_to_ros_time(pf.imu_gyro_ts(imu_buf)); + }}; + } + return [&pf, &frame, timestamper](const uint8_t* imu_buf) { return packet_to_imu_msg(pf, timestamper(imu_buf), frame, imu_buf); }; } }; -} // namespace ouster_ros \ No newline at end of file +} // namespace ouster_ros \ No newline at end of file diff --git a/src/lidar_packet_handler.h b/src/lidar_packet_handler.h index 9ee56005..7effc6ba 100644 --- a/src/lidar_packet_handler.h +++ b/src/lidar_packet_handler.h @@ -82,7 +82,6 @@ namespace sensor = ouster::sensor; using LidarScanProcessor = std::function; - class LidarPacketHandler { using LidarPacketAccumlator = std::function; @@ -93,7 +92,10 @@ class LidarPacketHandler { public: LidarPacketHandler(const ouster::sensor::sensor_info& info, - bool use_ros_time) { + const std::vector& handlers, + const std::string& timestamp_mode, + int64_t ptp_utc_tai_offset) + : lidar_scan_handlers{handlers} { // initialize lidar_scan processor and buffer scan_batcher = std::make_unique(info); lidar_scan = std::make_unique( @@ -107,14 +109,23 @@ class LidarPacketHandler { }; const sensor::packet_format& pf = sensor::get_format(info); - lidar_packet_accumlator = - use_ros_time - ? LidarPacketAccumlator{[this, pf](const uint8_t* lidar_buf) { - return lidar_handler_ros_time(pf, lidar_buf); - }} - : LidarPacketAccumlator{[this, pf](const uint8_t* lidar_buf) { - return lidar_handler_sensor_time(pf, lidar_buf); - }}; + if (timestamp_mode == "TIME_FROM_ROS_TIME") { + lidar_packet_accumlator = + LidarPacketAccumlator{[this, pf](const uint8_t* lidar_buf) { + return lidar_handler_ros_time(pf, lidar_buf); + }}; + } else if (timestamp_mode == "TIME_FROM_PTP_1588") { + lidar_packet_accumlator = LidarPacketAccumlator{ + [this, pf, ptp_utc_tai_offset](const uint8_t* lidar_buf) { + return lidar_handler_sensor_time_ptp(pf, lidar_buf, + ptp_utc_tai_offset); + }}; + } else { + lidar_packet_accumlator = + LidarPacketAccumlator{[this, pf](const uint8_t* lidar_buf) { + return lidar_handler_sensor_time(pf, lidar_buf); + }}; + } } LidarPacketHandler(const LidarPacketHandler&) = delete; @@ -129,12 +140,11 @@ class LidarPacketHandler { public: static HandlerType create_handler( - const ouster::sensor::sensor_info& info, bool use_ros_time, - const std::vector& handlers) { - auto handler = std::make_shared(info, use_ros_time); - - handler->lidar_scan_handlers = handlers; - + const ouster::sensor::sensor_info& info, + const std::vector& handlers, + const std::string& timestamp_mode, int64_t ptp_utc_tai_offset) { + auto handler = std::make_shared( + info, handlers, timestamp_mode, ptp_utc_tai_offset); return [handler](const uint8_t* lidar_buf) { if (handler->lidar_packet_accumlator(lidar_buf)) { for (auto h : handler->lidar_scan_handlers) { @@ -202,7 +212,6 @@ class LidarPacketHandler { assert(idx != ts_v.data() + ts_v.size()); // should never happen int curr_scan_first_nonzero_idx = idx - ts_v.data(); uint64_t curr_scan_first_nonzero_value = *idx; - uint64_t scan_ns = curr_scan_first_nonzero_idx == 0 ? curr_scan_first_nonzero_value : impute_value(last_scan_last_nonzero_idx, @@ -210,7 +219,6 @@ class LidarPacketHandler { curr_scan_first_nonzero_idx, curr_scan_first_nonzero_value, static_cast(ts_v.size())); - last_scan_last_nonzero_idx = find_if_reverse(ts_v, [](uint64_t h) { return h != 0; }); assert(last_scan_last_nonzero_idx >= 0); // should never happen @@ -242,6 +250,19 @@ class LidarPacketHandler { return true; } + bool lidar_handler_sensor_time_ptp(const sensor::packet_format&, + const uint8_t* lidar_buf, + int64_t ptp_utc_tai_offset) { + if (!(*scan_batcher)(lidar_buf, *lidar_scan)) return false; + auto ts_v = lidar_scan->timestamp(); + for (int i = 0; i < ts_v.rows(); ++i) + ts_v[i] = impl::ts_safe_offset_add(ts_v[i], ptp_utc_tai_offset); + lidar_scan_estimated_ts = compute_scan_ts(ts_v); + lidar_scan_estimated_msg_ts = + impl::ts_to_ros_time(lidar_scan_estimated_ts); + return true; + } + bool lidar_handler_ros_time(const sensor::packet_format& pf, const uint8_t* lidar_buf) { auto packet_receive_time = ros::Time::now(); @@ -289,4 +310,4 @@ class LidarPacketHandler { LidarPacketAccumlator lidar_packet_accumlator; }; -} // namespace ouster_ros \ No newline at end of file +} // namespace ouster_ros \ No newline at end of file diff --git a/src/os_cloud_nodelet.cpp b/src/os_cloud_nodelet.cpp index b6ea552d..75deb21f 100644 --- a/src/os_cloud_nodelet.cpp +++ b/src/os_cloud_nodelet.cpp @@ -93,15 +93,16 @@ class OusterCloud : public nodelet::Nodelet { auto proc_mask = pnh.param("proc_mask", std::string{"IMU|PCL|SCAN"}); auto tokens = parse_tokens(proc_mask, '|'); - auto timestamp_mode_arg = pnh.param("timestamp_mode", std::string{}); - bool use_ros_time = timestamp_mode_arg == "TIME_FROM_ROS_TIME"; + auto timestamp_mode = pnh.param("timestamp_mode", std::string{}); + double ptp_utc_tai_offset = pnh.param("ptp_utc_tai_offset", -37.0); auto& nh = getNodeHandle(); if (check_token(tokens, "IMU")) { imu_pub = nh.advertise("imu", 100); imu_packet_handler = ImuPacketHandler::create_handler( - info, tf_bcast.imu_frame_id(), use_ros_time); + info, tf_bcast.imu_frame_id(), timestamp_mode, + static_cast(ptp_utc_tai_offset * 1e+9)); imu_packet_sub = nh.subscribe( "imu_packets", 100, [this](const PacketMsg::ConstPtr msg) { auto imu_msg = imu_packet_handler(msg->buf.data()); @@ -152,12 +153,8 @@ class OusterCloud : public nodelet::Nodelet { } processors.push_back(LaserScanProcessor::create( - info, - tf_bcast - .point_cloud_frame_id(), // TODO: should we allow having a - // different frame for the laser - // scan than point cloud??? - scan_ring, [this](LaserScanProcessor::OutputType msgs) { + info, tf_bcast.lidar_frame_id(), scan_ring, + [this](LaserScanProcessor::OutputType msgs) { for (size_t i = 0; i < msgs.size(); ++i) { if (msgs[i]->header.stamp > last_msg_ts) last_msg_ts = msgs[i]->header.stamp; @@ -168,7 +165,8 @@ class OusterCloud : public nodelet::Nodelet { if (check_token(tokens, "PCL") || check_token(tokens, "SCAN")) { lidar_packet_handler = LidarPacketHandler::create_handler( - info, use_ros_time, processors); + info, processors, timestamp_mode, + static_cast(ptp_utc_tai_offset * 1e+9)); lidar_packet_sub = nh.subscribe( "lidar_packets", 100, [this](const PacketMsg::ConstPtr msg) { lidar_packet_handler(msg->buf.data()); diff --git a/src/os_driver_nodelet.cpp b/src/os_driver_nodelet.cpp index 65d15074..8c602cee 100644 --- a/src/os_driver_nodelet.cpp +++ b/src/os_driver_nodelet.cpp @@ -52,15 +52,16 @@ class OusterDriver : public OusterSensor { pnh.param("proc_mask", std::string{"IMU|IMG|PCL|SCAN"}); auto tokens = parse_tokens(proc_mask, '|'); - auto timestamp_mode_arg = pnh.param("timestamp_mode", std::string{}); - bool use_ros_time = timestamp_mode_arg == "TIME_FROM_ROS_TIME"; + auto timestamp_mode = pnh.param("timestamp_mode", std::string{}); + double ptp_utc_tai_offset = pnh.param("ptp_utc_tai_offset", -37.0); auto& nh = getNodeHandle(); if (check_token(tokens, "IMU")) { imu_pub = nh.advertise("imu", 100); imu_packet_handler = ImuPacketHandler::create_handler( - info, tf_bcast.imu_frame_id(), use_ros_time); + info, tf_bcast.imu_frame_id(), timestamp_mode, + static_cast(ptp_utc_tai_offset * 1e+9)); } int num_returns = get_n_returns(info); @@ -102,12 +103,8 @@ class OusterDriver : public OusterSensor { } processors.push_back(LaserScanProcessor::create( - info, - tf_bcast - .point_cloud_frame_id(), // TODO: should we allow having a - // different frame for the laser - // scan than point cloud??? - scan_ring, [this](LaserScanProcessor::OutputType msgs) { + info, tf_bcast.lidar_frame_id(), scan_ring, + [this](LaserScanProcessor::OutputType msgs) { for (size_t i = 0; i < msgs.size(); ++i) { scan_pubs[i].publish(*msgs[i]); } @@ -151,7 +148,8 @@ class OusterDriver : public OusterSensor { if (check_token(tokens, "PCL") || check_token(tokens, "SCAN") || check_token(tokens, "IMG")) lidar_packet_handler = LidarPacketHandler::create_handler( - info, use_ros_time, processors); + info, processors, timestamp_mode, + static_cast(ptp_utc_tai_offset * 1e+9)); } virtual void on_lidar_packet_msg(const uint8_t* raw_lidar_packet) override { diff --git a/src/os_ros.cpp b/src/os_ros.cpp index 10fe847a..63e88593 100644 --- a/src/os_ros.cpp +++ b/src/os_ros.cpp @@ -112,12 +112,6 @@ sensor::ChanField suitable_return(sensor::ChanField input_field, bool second) { } } -ros::Time ts_to_ros_time(uint64_t ts) { - ros::Time t; - t.fromNSec(ts); - return t; -} - } // namespace impl template & near_ir, const ouster::img_t& signal, const std::vector& pixel_shift_by_row) { auto timestamp = ls.timestamp(); - const auto rg = range.data(); const auto rf = reflectivity.data(); const auto nr = near_ir.data(); @@ -216,11 +209,9 @@ void copy_scan_to_cloud_destaggered( #endif for (auto u = 0; u < ls.h; u++) { for (auto v = 0; v < ls.w; v++) { - const auto ts_src_idx = (v + ls.w - pixel_shift_by_row[u]) % ls.w; - const auto col_ts = timestamp[ts_src_idx]; - const auto ts = col_ts > scan_ts ? col_ts - scan_ts : 0UL; - const auto src_idx = - u * ls.w + (v + ls.w - pixel_shift_by_row[u]) % ls.w; + const auto v_shift = (v + ls.w - pixel_shift_by_row[u]) % ls.w; + auto ts = timestamp[v_shift]; ts = ts > scan_ts ? ts - scan_ts : 0UL; + const auto src_idx = u * ls.w + v_shift; const auto tgt_idx = u * ls.w + v; const auto xyz = points.row(src_idx); cloud.points[tgt_idx] = ouster_ros::Point{ diff --git a/src/os_sensor_nodelet_base.cpp b/src/os_sensor_nodelet_base.cpp index b4681c62..6f4b1aec 100644 --- a/src/os_sensor_nodelet_base.cpp +++ b/src/os_sensor_nodelet_base.cpp @@ -6,13 +6,13 @@ * @brief implementation of OusterSensorNodeletBase interface */ -#include - #include "ouster_ros/os_sensor_nodelet_base.h" #include #include +#include + #include "ouster_ros/GetMetadata.h" namespace sensor = ouster::sensor; diff --git a/src/os_transforms_broadcaster.h b/src/os_transforms_broadcaster.h index 7213b553..ca6f12d2 100644 --- a/src/os_transforms_broadcaster.h +++ b/src/os_transforms_broadcaster.h @@ -89,6 +89,9 @@ class OusterTransformsBroadcaster { } const std::string& imu_frame_id() const { return imu_frame; } + const std::string& lidar_frame_id() const { return lidar_frame; } + const std::string& sensor_frame_id() const { return sensor_frame; } + const std::string& point_cloud_frame_id() const { return point_cloud_frame; } @@ -100,9 +103,9 @@ class OusterTransformsBroadcaster { std::string node_name; tf2_ros::StaticTransformBroadcaster static_tf_bcast; tf2_ros::TransformBroadcaster tf_bcast; // non-static - std::string sensor_frame; std::string imu_frame; std::string lidar_frame; + std::string sensor_frame; std::string point_cloud_frame; }; diff --git a/src/point_cloud_processor.h b/src/point_cloud_processor.h index 7990c31a..f8e0fa0f 100644 --- a/src/point_cloud_processor.h +++ b/src/point_cloud_processor.h @@ -62,9 +62,9 @@ class PointCloudProcessor { void process(const ouster::LidarScan& lidar_scan, uint64_t scan_ts, const ros::Time& msg_ts) { for (int i = 0; i < static_cast(pc_msgs.size()); ++i) { - scan_to_cloud_f_destaggered(cloud, - points, lut_direction, lut_offset, - scan_ts, lidar_scan, pixel_shift_by_row, i); + scan_to_cloud_f_destaggered(cloud, points, lut_direction, + lut_offset, scan_ts, lidar_scan, + pixel_shift_by_row, i); pcl_toROSMsg(cloud, *pc_msgs[i]); pc_msgs[i]->header.stamp = msg_ts; pc_msgs[i]->header.frame_id = frame; @@ -105,4 +105,4 @@ class PointCloudProcessor { PostProcessingFn post_processing_fn; }; -} // namespace ouster_ros \ No newline at end of file +} // namespace ouster_ros \ No newline at end of file