Program Listing for File sensor.cpp

Return to documentation for file (src/sensor.cpp)

// Copyright 2023 Universidad Politécnica de Madrid
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright
//      notice, this list of conditions and the following disclaimer.
//
//    * Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in the
//      documentation and/or other materials provided with the distribution.
//
//    * Neither the name of the Universidad Politécnica de Madrid nor the names of its
//      contributors may be used to endorse or promote products derived from
//      this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

/*!*******************************************************************************************
 *  \file       sensor.cpp
 *  \brief      Sensor class for AS2 implementation file.
 *  \authors    Miguel Fernández Cortizas
 *              Pedro Arias Pérez
 *              David Pérez Saura
 *              Rafael Pérez Seguí
 ********************************************************************************/

#include "as2_core/sensor.hpp"

namespace as2
{
namespace sensors
{

// TFStatic

TFStatic::TFStatic(rclcpp::Node * node_ptr)
: node_ptr_(node_ptr) {}

TFStatic::~TFStatic() {}

void TFStatic::setStaticTransform(
  const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
  float roll, float pitch, float yaw)
{
  // convert to quaternion and set the transform
  tf2::Quaternion q;
  q.setRPY(roll, pitch, yaw);
  setStaticTransform(frame_id, parent_frame_id, x, y, z, q.x(), q.y(), q.z(), q.w());
}

void TFStatic::setStaticTransform(
  const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
  float qx, float qy, float qz, float qw)
{
  geometry_msgs::msg::TransformStamped transformStamped;
  transformStamped.header.stamp = node_ptr_->now();
  transformStamped.header.frame_id = parent_frame_id;
  transformStamped.child_frame_id = frame_id;
  transformStamped.transform.translation.x = x;
  transformStamped.transform.translation.y = y;
  transformStamped.transform.translation.z = z;
  transformStamped.transform.rotation.x = qx;
  transformStamped.transform.rotation.y = qy;
  transformStamped.transform.rotation.z = qz;
  transformStamped.transform.rotation.w = qw;
  setStaticTransform_(transformStamped);
}

void TFStatic::setStaticTransform(
  const geometry_msgs::msg::TransformStamped & transformStamped)
{
  setStaticTransform_(transformStamped);
}

void TFStatic::setStaticTransform_(
  const geometry_msgs::msg::TransformStamped & transformStamped)
{
  static tf2_ros::StaticTransformBroadcaster static_broadcaster(node_ptr_);
  static_broadcaster.sendTransform(transformStamped);
  RCLCPP_INFO(
    node_ptr_->get_logger(), "Static transform published: %s -> %s",
    transformStamped.header.frame_id.c_str(),
    transformStamped.child_frame_id.c_str());
}

const rclcpp::Node * TFStatic::getNode() const
{
  return node_ptr_;
}

// TFDynamic

TFDynamic::TFDynamic(rclcpp::Node * node_ptr)
: node_ptr_(node_ptr)
{
  dynamic_tf_broadcaster_ptr_ = std::make_shared<tf2_ros::TransformBroadcaster>(node_ptr);
}

TFDynamic::~TFDynamic()
{
  dynamic_tf_broadcaster_ptr_.reset();
}

void TFDynamic::setDynamicTransform(const geometry_msgs::msg::TransformStamped & transform)
{
  dynamic_tf_broadcaster_ptr_->sendTransform(transform);
}

void TFDynamic::setDynamicTransform(
  const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
  float qx, float qy, float qz, float qw)
{
  geometry_msgs::msg::TransformStamped transform;
  transform.header.stamp = node_ptr_->now();
  transform.header.frame_id = parent_frame_id;
  transform.child_frame_id = frame_id;
  transform.transform.translation.x = x;
  transform.transform.translation.y = y;
  transform.transform.translation.z = z;
  transform.transform.rotation.x = qx;
  transform.transform.rotation.y = qy;
  transform.transform.rotation.z = qz;
  transform.transform.rotation.w = qw;
  setDynamicTransform(transform);
}

void TFDynamic::setDynamicTransform(
  const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
  float roll, float pitch, float yaw)
{
  tf2::Quaternion q;
  q.setRPY(roll, pitch, yaw);
  setDynamicTransform(frame_id, parent_frame_id, x, y, z, q.x(), q.y(), q.z(), q.w());
}

std::shared_ptr<tf2_ros::TransformBroadcaster> TFDynamic::getTransformBroadcaster() const
{
  return dynamic_tf_broadcaster_ptr_;
}

const rclcpp::Node * TFDynamic::getNode() const
{
  return node_ptr_;
}

// GenericSensor

GenericSensor::GenericSensor(as2::Node * node_ptr, const float pub_freq)
:  pub_freq_(pub_freq)
{
  if (pub_freq > 0.0f) {
    timer_ = node_ptr->create_timer(
      std::chrono::duration<double>(1.0 / pub_freq),
      std::bind(&GenericSensor::timerCallback, this));
  }
}

GenericSensor::~GenericSensor()
{
  timer_.reset();
}

void GenericSensor::dataUpdated()
{
  if (pub_freq_ <= 0.0f) {
    publishData();
  }
  // else: Timer will publish the data
}

void GenericSensor::timerCallback()
{
  publishData();
}

// Camera

Camera::Camera(
  const std::string & id, as2::Node * node_ptr, const float pub_freq,
  bool add_sensor_measurements_base,
  const std::string & info_name,
  const std::string & camera_link)
: TFStatic(node_ptr), GenericSensor(node_ptr, pub_freq), node_ptr_(node_ptr), camera_link_(
    camera_link)
{
  camera_base_topic_ = SensorData<sensor_msgs::msg::CameraInfo>::processTopicName(
    id, add_sensor_measurements_base);

  camera_info_sensor_ = std::make_shared<SensorData<sensor_msgs::msg::CameraInfo>>(
    camera_base_topic_ + '/' + info_name, node_ptr, false);

  camera_frame_ = as2::tf::generateTfName(node_ptr_->get_namespace(), id + "/" + camera_link);
}

Camera::~Camera()
{
  image_transport_ptr_.reset();
  camera_info_sensor_.reset();
}

std::shared_ptr<rclcpp::Node> Camera::getSelfPtr() {return node_ptr_->shared_from_this();}

void Camera::setup()
{
  image_transport_ptr_ = std::make_shared<image_transport::ImageTransport>(getSelfPtr());
  image_transport::ImageTransport & image_transport_ = *image_transport_ptr_;
  it_publisher_ = image_transport_.advertise(camera_base_topic_, 1);
  setup_ = true;
}

void Camera::publishData()
{
  it_publisher_.publish(image_data_);
  if (camera_info_available_) {
    camera_info_sensor_->publish();
  }
}

void Camera::updateData(const sensor_msgs::msg::Image & img)
{
  if (!setup_) {
    setup();
  }

  image_data_ = img;
  dataUpdated();
}

void Camera::updateData(const cv::Mat & img)
{
  sensor_msgs::msg::Image img_msg;
  cv_bridge::CvImage cv_image;
  cv_image.header.stamp = node_ptr_->now();
  cv_image.header.frame_id = camera_frame_;
  cv_image.encoding = encoding_;
  cv_image.image = img;
  cv_image.toImageMsg(img_msg);

  updateData(img_msg);
}

void Camera::setParameters(
  const sensor_msgs::msg::CameraInfo & camera_info, const std::string & encoding,
  const std::string & camera_model)
{
  encoding_ = encoding;
  camera_model_ = camera_model;
  camera_info_sensor_->setData(camera_info);
  camera_info_available_ = true;
}

void Camera::setStaticTransform(
  const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
  float qx, float qy, float qz, float qw)
{
  // TODO(perezsaura-david): enhance performance. Obtain r,p and yaw from quaternion
  tf2::Quaternion q(qx, qy, qz, qw);
  tf2::Matrix3x3 m(q);
  double roll, pitch, yaw;
  m.getRPY(roll, pitch, yaw);
  this->setStaticTransform(frame_id, parent_frame_id, x, y, z, roll, pitch, yaw);
}

void Camera::setStaticTransform(
  const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
  float roll, float pitch, float yaw)
{
  // convert to quaternion and set the transform
  tf2::Quaternion q;
  q.setRPY(roll, pitch, yaw);
  TFStatic::setStaticTransform(frame_id, parent_frame_id, x, y, z, q.x(), q.y(), q.z(), q.w());

  // if frame_id does not contain "camera_link"
  if (frame_id.find(camera_link_) == std::string::npos) {
    // set the static transform for the camera_link frame rotating from FLU  to RDF
    yaw = -M_PI / 2.0f;
    pitch = 0;
    roll = -M_PI / 2.0f;
    q.setRPY(roll, pitch, yaw);
    TFStatic::setStaticTransform(
      frame_id + "/" + camera_link_, frame_id, 0, 0, 0, q.x(), q.y(), q.z(), q.w());
  }
}

// Grount Truth Sensor

GroundTruth::GroundTruth(
  as2::Node * node_ptr, const float pub_freq,
  const std::string & topic_name_base)
: GenericSensor(node_ptr, pub_freq)
{
  const std::string pose_topic_name = topic_name_base + as2_names::topics::ground_truth::pose;
  const std::string twist_topic_name = topic_name_base + as2_names::topics::ground_truth::twist;

  pose_sensor_ = std::make_shared<SensorData<geometry_msgs::msg::PoseStamped>>(
    pose_topic_name, node_ptr, false);
  twist_sensor_ = std::make_shared<SensorData<geometry_msgs::msg::TwistStamped>>(
    twist_topic_name, node_ptr, false);
}

GroundTruth::~GroundTruth()
{
  // Clean up ROS 2 publishers
  pose_sensor_.reset();
  twist_sensor_.reset();
}

void GroundTruth::updateData(const geometry_msgs::msg::PoseStamped & pose_msg)
{
  pose_sensor_->setData(pose_msg);
  dataUpdated();
}

void GroundTruth::updateData(const geometry_msgs::msg::TwistStamped & twist_msg)
{
  twist_sensor_->setData(twist_msg);
  dataUpdated();
}

void GroundTruth::updateData(
  const geometry_msgs::msg::PoseStamped & pose_msg,
  const geometry_msgs::msg::TwistStamped & twist_msg)
{
  pose_sensor_->setData(pose_msg);
  twist_sensor_->setData(twist_msg);
  dataUpdated();
}

void GroundTruth::publishData()
{
  pose_sensor_->publish();
  twist_sensor_->publish();
}

// Gimbal

Gimbal::Gimbal(
  const std::string & gimbal_id,
  const std::string & gimbal_base_id,
  as2::Node * node_ptr,
  const float pub_freq,
  bool add_sensor_measurements_base)
: TFStatic(node_ptr), TFDynamic(node_ptr), GenericSensor(node_ptr, pub_freq),
  SensorData<geometry_msgs::msg::PoseStamped>(gimbal_id, node_ptr, add_sensor_measurements_base)
{
  gimbal_frame_id_ = as2::tf::generateTfName(node_ptr->get_namespace(), gimbal_id);
  gimbal_base_frame_id_ = as2::tf::generateTfName(node_ptr->get_namespace(), gimbal_base_id);
}

Gimbal::~Gimbal() {}

void Gimbal::setGimbalBaseTransform(
  const geometry_msgs::msg::Transform & gimbal_base_transform,
  const std::string & gimbal_parent_frame_id)
{
  // Set static transform between gimbal_base and gimbal_parent_frame_id
  std::string _gimbal_parent_frame_id = as2::tf::generateTfName(
    TFStatic::getNode()->get_namespace(), gimbal_parent_frame_id);
  geometry_msgs::msg::TransformStamped gimbal_base_transform_stamped;
  gimbal_base_transform_stamped.header.stamp = TFStatic::getNode()->now();
  gimbal_base_transform_stamped.header.frame_id = _gimbal_parent_frame_id;
  gimbal_base_transform_stamped.child_frame_id = gimbal_base_frame_id_;
  gimbal_base_transform_stamped.transform = gimbal_base_transform;
  setStaticTransform(gimbal_base_transform_stamped);
}

void Gimbal::updateData(const geometry_msgs::msg::PoseStamped & pose_msg)
{
  SensorData<geometry_msgs::msg::PoseStamped>::setData(pose_msg);
  gimbal_transform_.header.stamp = pose_msg.header.stamp;
  gimbal_transform_.header.frame_id = gimbal_base_frame_id_;
  gimbal_transform_.child_frame_id = gimbal_frame_id_;
  gimbal_transform_.transform.translation.x = pose_msg.pose.position.x;
  gimbal_transform_.transform.translation.y = pose_msg.pose.position.y;
  gimbal_transform_.transform.translation.z = pose_msg.pose.position.z;
  gimbal_transform_.transform.rotation = pose_msg.pose.orientation;
  GenericSensor::dataUpdated();
}

void Gimbal::updateData(const geometry_msgs::msg::QuaternionStamped & orientation_msg)
{
  geometry_msgs::msg::PoseStamped pose_msg;
  pose_msg.header = orientation_msg.header;
  pose_msg.pose.position.x = gimbal_transform_.transform.translation.x;
  pose_msg.pose.position.y = gimbal_transform_.transform.translation.y;
  pose_msg.pose.position.z = gimbal_transform_.transform.translation.z;
  pose_msg.pose.orientation = orientation_msg.quaternion;
  updateData(pose_msg);
}

const std::string & Gimbal::getGimbalFrameId() const
{
  return gimbal_frame_id_;
}

const std::string & Gimbal::getGimbalBaseFrameId() const
{
  return gimbal_base_frame_id_;
}

void Gimbal::publishData()
{
  SensorData<geometry_msgs::msg::PoseStamped>::publish();
  setDynamicTransform(gimbal_transform_);
}


}  // namespace sensors
}  // namespace as2