Program Listing for File sensor.hpp

Return to documentation for file (/tmp/ws/src/aerostack2/as2_core/include/as2_core/sensor.hpp)

// 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.hpp
 *  \brief      Sensor class for AS2 header file
 *  \authors    Rafael Pérez Seguí
 *              David Pérez Saura
 *              Miguel Fernández Cortizas
 *              Pedro Arias Pérez
 ********************************************************************************/

#ifndef AS2_CORE__SENSOR_HPP_
#define AS2_CORE__SENSOR_HPP_

#include <cv_bridge/cv_bridge.h>
#include <tf2_ros/static_transform_broadcaster.h>
#include <tf2_ros/transform_broadcaster.h>
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include <opencv2/opencv.hpp>
#include <image_transport/image_transport.hpp>
#include <rclcpp/publisher.hpp>
#include <rclcpp/publisher_options.hpp>
#include <rclcpp/rclcpp.hpp>
#include <sensor_msgs/msg/battery_state.hpp>
#include <sensor_msgs/msg/camera_info.hpp>
#include <sensor_msgs/msg/fluid_pressure.hpp>
#include <sensor_msgs/msg/image.hpp>
#include <sensor_msgs/msg/imu.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>
#include <sensor_msgs/msg/magnetic_field.hpp>
#include <sensor_msgs/msg/nav_sat_fix.hpp>
#include <sensor_msgs/msg/range.hpp>
#include <geometry_msgs/msg/pose_stamped.hpp>
#include <geometry_msgs/msg/twist_stamped.hpp>
#include <geometry_msgs/msg/quaternion_stamped.hpp>
#include <nav_msgs/msg/odometry.hpp>
#include "as2_core/custom/tf2_geometry_msgs.hpp"
#include "as2_core/names/topics.hpp"
#include "as2_core/node.hpp"
#include "as2_core/utils/tf_utils.hpp"

namespace as2
{
namespace sensors
{

class TFStatic
{
public:
  explicit TFStatic(rclcpp::Node * node_ptr);

  virtual ~TFStatic();

  virtual void setStaticTransform(
    const geometry_msgs::msg::TransformStamped & transformStamped);

  virtual void 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);

  virtual void setStaticTransform(
    const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
    float roll, float pitch, float yaw);

  const rclcpp::Node * getNode() const;

private:
  rclcpp::Node * node_ptr_ = nullptr;

  virtual void setStaticTransform_(const geometry_msgs::msg::TransformStamped & transformStamped);
};  // class TFStatic

class TFDynamic
{
public:
  explicit TFDynamic(rclcpp::Node * node_ptr);

  virtual ~TFDynamic();

  virtual void setDynamicTransform(const geometry_msgs::msg::TransformStamped & transform);

  virtual void 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);

  virtual void setDynamicTransform(
    const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
    float roll, float pitch, float yaw);

  std::shared_ptr<tf2_ros::TransformBroadcaster> getTransformBroadcaster() const;

  const rclcpp::Node * getNode() const;

protected:
  rclcpp::Node * node_ptr_ = nullptr;
  std::shared_ptr<tf2_ros::TransformBroadcaster> dynamic_tf_broadcaster_ptr_;
};  // class TFDynamic

template<typename T>
class SensorData
{
public:
  explicit SensorData(
    const std::string & topic_name, rclcpp::Node * node_ptr,
    bool add_sensor_measurements_base = true)
  {
    // check if topic already has "sensor_measurements "in the name
    // if not, add it if flag add_sensor_measurements_base is true
    topic_name_ = processTopicName(topic_name, add_sensor_measurements_base);

    sensor_publisher_ = node_ptr->create_publisher<T>(
      topic_name_, as2_names::topics::sensor_measurements::qos);
  }

  virtual ~SensorData() {}

  static std::string processTopicName(
    const std::string & topic_name,
    bool add_sensor_measurements_base = true)
  {
    std::string topic = topic_name;
    if (add_sensor_measurements_base &&
      topic_name.find(as2_names::topics::sensor_measurements::base) == std::string::npos)
    {
      topic = as2_names::topics::sensor_measurements::base + topic_name;
    }
    return topic;
  }

  void setData(const T & msg)
  {
    msg_data_ = msg;
  }

  void publish()
  {
    sensor_publisher_->publish(msg_data_);
  }

  void updateAndPublish(const T & msg)
  {
    msg_data_ = msg;
    publish();
  }

  const std::string & getTopicName() const
  {
    return topic_name_;
  }

  const T & getData() const
  {
    return msg_data_;
  }

private:
  typename rclcpp::Publisher<T>::SharedPtr sensor_publisher_;
  T msg_data_;
  std::string topic_name_;
};  // class SensorData

class GenericSensor
{
public:
  explicit GenericSensor(as2::Node * node_ptr, const float pub_freq = -1.0f);

  virtual ~GenericSensor();

  void dataUpdated();

  virtual void publishData() = 0;

private:
  rclcpp::TimerBase::SharedPtr timer_;
  float pub_freq_;

  void timerCallback();
};

template<typename T>
class Sensor : public TFStatic, protected GenericSensor, protected SensorData<T>
{
public:
  Sensor(
    const std::string & id, as2::Node * node_ptr, float pub_freq = -1.0f,
    bool add_sensor_measurements_base = true)
  : TFStatic(node_ptr),
    GenericSensor(node_ptr, pub_freq),
    SensorData<T>(id, node_ptr, add_sensor_measurements_base)
  {}

  Sensor(
    const std::string & id, as2::Node * node_ptr, int pub_freq,
    bool add_sensor_measurements_base = true)
  : Sensor(id, node_ptr, static_cast<float>(pub_freq), add_sensor_measurements_base)
  {}

  Sensor(
    const std::string & id, as2::Node * node_ptr, double pub_freq,
    bool add_sensor_measurements_base = true)
  : Sensor(id, node_ptr, static_cast<float>(pub_freq), add_sensor_measurements_base)
  {}

  virtual ~Sensor() {}

  void updateData(const T & msg)
  {
    SensorData<T>::setData(msg);
    dataUpdated();
  }

protected:
  void publishData() override
  {
    SensorData<T>::publish();
  }
};  // class Sensor

class Camera : public TFStatic, protected GenericSensor
{
public:
  Camera(
    const std::string & id, as2::Node * node_ptr, const float pub_freq = -1.0f,
    bool add_sensor_measurements_base = true,
    const std::string & info_name = "camera_info",
    const std::string & camera_link = "camera_link");

  virtual ~Camera();

  void updateData(const sensor_msgs::msg::Image & img);

  void updateData(const cv::Mat & img);

  void setParameters(
    const sensor_msgs::msg::CameraInfo & camera_info, const std::string & encoding,
    const std::string & camera_model = "pinhole");

  void 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);  // Shadowing the TFStatic function

  void setStaticTransform(
    const std::string & frame_id, const std::string & parent_frame_id, float x, float y, float z,
    float roll, float pitch, float yaw);  // Shadowing the TFStatic function

  // TODO(perezsaura-david): Implement the following functions
  // void loadParameters(const std::string & file)
  // void publishRectifiedImage(const sensor_msgs::msg::Image & msg)
  // void publishCompressedImage(const sensor_msgs::msg::Image & msg)

private:
  as2::Node * node_ptr_ = nullptr;
  std::string camera_link_;

  std::string camera_base_topic_;
  std::string camera_frame_;

  // Camera info
  std::shared_ptr<SensorData<sensor_msgs::msg::CameraInfo>> camera_info_sensor_;
  bool setup_ = false;
  bool camera_info_available_ = false;
  std::string encoding_;
  std::string camera_model_;
  std::string camera_name_;

  // Camera image
  rclcpp::Publisher<sensor_msgs::msg::Image>::SharedPtr image_publisher_;
  std::shared_ptr<image_transport::ImageTransport> image_transport_ptr_ = nullptr;
  image_transport::Publisher it_publisher_;
  sensor_msgs::msg::Image image_data_;

  std::shared_ptr<rclcpp::Node> getSelfPtr();

  void setup();

  void publishData();
};  // class CameraSensor

class GroundTruth : protected GenericSensor
{
public:
  explicit GroundTruth(
    as2::Node * node_ptr, const float pub_freq = -1,
    const std::string & topic_name_base = "");

  virtual ~GroundTruth();

  void updateData(const geometry_msgs::msg::PoseStamped & pose_msg);

  void updateData(const geometry_msgs::msg::TwistStamped & twist_msg);

  void updateData(
    const geometry_msgs::msg::PoseStamped & pose_msg,
    const geometry_msgs::msg::TwistStamped & twist_msg);

protected:
  std::shared_ptr<SensorData<geometry_msgs::msg::PoseStamped>> pose_sensor_;
  std::shared_ptr<SensorData<geometry_msgs::msg::TwistStamped>> twist_sensor_;

  void publishData() override;
};  // class GroundTruth

class Gimbal : public TFStatic, protected TFDynamic, protected GenericSensor,
  protected SensorData<geometry_msgs::msg::PoseStamped>
{
public:
  explicit Gimbal(
    const std::string & gimbal_id,
    const std::string & gimbal_base_id,
    as2::Node * node_ptr,
    const float pub_freq = -1.0f,
    bool add_sensor_measurements_base = true);

  virtual ~Gimbal();

  void setGimbalBaseTransform(
    const geometry_msgs::msg::Transform & gimbal_base_transform,
    const std::string & gimbal_parent_frame_id = "base_link");

  void updateData(const geometry_msgs::msg::PoseStamped & pose_msg);

  void updateData(const geometry_msgs::msg::QuaternionStamped & orientation_msg);

  const std::string & getGimbalFrameId() const;

  const std::string & getGimbalBaseFrameId() const;

protected:
  std::string gimbal_frame_id_;
  std::string gimbal_base_frame_id_;
  geometry_msgs::msg::TransformStamped gimbal_transform_;

  void publishData();
};  // class Gimbal

using Odometry = Sensor<nav_msgs::msg::Odometry>;
using Imu = Sensor<sensor_msgs::msg::Imu>;
using GPS = Sensor<sensor_msgs::msg::NavSatFix>;
using Lidar = Sensor<sensor_msgs::msg::LaserScan>;
using Battery = Sensor<sensor_msgs::msg::BatteryState>;
using Barometer = Sensor<sensor_msgs::msg::FluidPressure>;
using Compass = Sensor<sensor_msgs::msg::MagneticField>;
using RangeFinder = Sensor<sensor_msgs::msg::Range>;

}  // namespace sensors
}  // namespace as2

#endif  // AS2_CORE__SENSOR_HPP_