Program Listing for File dji_subscriber.hpp
↰ Return to documentation for file (/tmp/ws/src/aerostack2/as2_aerial_platforms/as2_platform_dji_osdk/include/dji_subscriber.hpp
)
#ifndef ___DJI_SUBSCRIBER_HPP___
#define ___DJI_SUBSCRIBER_HPP___
#include <as2_core/sensor.hpp>
#include <as2_core/utils/frame_utils.hpp>
#include <as2_core/utils/tf_utils.hpp>
#include <cmath>
#include <memory>
#include <rclcpp/logging.hpp>
#include <sensor_msgs/msg/detail/magnetic_field__struct.hpp>
#include "as2_core/node.hpp"
#include "as2_core/sensor.hpp"
#include "as2_core/utils/gps_utils.hpp"
#include "dji_linux_helpers.hpp"
#include "dji_telemetry.hpp"
#include "dji_vehicle.hpp"
#include "osdk_platform.h"
#include "osdkhal_linux.h"
#include "rclcpp/rclcpp.hpp"
#include "sensor_msgs/msg/nav_sat_fix.hpp"
#define RESPONSE_TIMEOUT 1
using namespace DJI::OSDK;
using namespace DJI::OSDK::Telemetry;
class DJISubscription {
static int n_packages_;
std::shared_ptr<rclcpp::TimerBase> update_timer_;
int package_index_;
int frequency_;
bool enable_timestamp_ = false;
bool started_ = false;
std::string name_;
public:
DJISubscription(const std::string package_name, as2::Node *&node,
Vehicle *vehicle, int frequency,
bool enable_timestamp = false)
: name_(package_name),
node_(node),
vehicle_(vehicle),
frequency_(frequency),
enable_timestamp_(enable_timestamp) {
if (vehicle_ == nullptr) throw std::runtime_error("Vehicle is nullptr");
n_packages_++;
package_index_ = n_packages_;
};
using SharedPtr = std::shared_ptr<DJISubscription>;
private:
protected:
virtual void onStart(){};
virtual void initializeTopics() = 0;
virtual void onUpdate() = 0;
virtual void onStop(){};
public:
bool start() {
initializeTopics();
onStart();
// TopicName test[] = {TOPIC_RTK_CONNECT_STATUS};
// int test_size = sizeof(test) / sizeof(test[0]);
// RCLCPP_INFO(node_->get_logger(), "test size: %d", test_size);
// // print vehicle_ ptr address
// RCLCPP_INFO(node_->get_logger(), "vehicle ptr address: %p", vehicle_);
auto subscribeStatus = vehicle_->subscribe->verify(1);
if (ACK::getError(subscribeStatus) != ACK::SUCCESS) {
ACK::getErrorCodeMessage(subscribeStatus, __func__);
return false;
}
// RCLCPP_INFO(node_->get_logger(), "subscribe status: %d",
// subscribeStatus);
bool pkgStatus = vehicle_->subscribe->initPackageFromTopicList(
getPackageIndex(), topics_.size(), topics_.data(), getEnableTimestamp(),
getFrequency());
// bool pkgStatus = vehicle_->subscribe->initPackageFromTopicList(
// getPackageIndex(), test_size, (), getPackageIndex(),
// getEnableTimestamp());
if (!pkgStatus) {
RCLCPP_ERROR(node_->get_logger(), "Failed to initialize package %d : %s ",
getPackageIndex(), getName().c_str());
return pkgStatus;
}
auto status =
vehicle_->subscribe->startPackage(getPackageIndex(), RESPONSE_TIMEOUT);
if (status.data == ErrorCode::SubscribeACK::SOURCE_DEVICE_OFFLINE) {
RCLCPP_WARN(node_->get_logger(), "%s is not available",
getName().c_str());
return false;
}
if (ACK::getError(status) != ACK::SUCCESS) {
ACK::getErrorCodeMessage(status, __func__);
// Cleanup before return
vehicle_->subscribe->removePackage(getPackageIndex(), RESPONSE_TIMEOUT);
return false;
};
update_timer_ =
node_->create_timer(std::chrono::duration<double>(1.0f / frequency_),
std::bind(&DJISubscription::update, this));
started_ = true;
return true;
};
void stop() {
update_timer_->cancel();
vehicle_->subscribe->removePackage(getPackageIndex(), RESPONSE_TIMEOUT);
onStop();
};
void update() {
if (!started_) {
return;
}
onUpdate();
};
inline int getPackageIndex() const { return package_index_; };
inline float getFrequency() const { return frequency_; };
inline void setFrequency(const float frequency) { frequency_ = frequency; };
inline bool getEnableTimestamp() const { return enable_timestamp_; };
inline std::string getName() const { return name_; };
protected:
Vehicle *vehicle_ = nullptr;
std::vector<TopicName> topics_;
as2::Node *node_;
};
class DJISubscriptionRTK : public DJISubscription {
private:
as2::sensors::GPS rtk_;
sensor_msgs::msg::NavSatFix rtk_msg_;
public:
DJISubscriptionRTK(as2::Node *node, Vehicle *vehicle, int frequency = 50,
bool enable_timestamp = false)
: DJISubscription("RTK", node, vehicle, frequency, enable_timestamp),
rtk_("rtk", node){};
protected:
void initializeTopics() override {
topics_ = {TOPIC_RTK_CONNECT_STATUS, TOPIC_RTK_POSITION, TOPIC_RTK_YAW_INFO,
TOPIC_RTK_POSITION_INFO, TOPIC_RTK_VELOCITY, TOPIC_RTK_YAW};
};
void onUpdate() override {
TypeMap<TOPIC_RTK_CONNECT_STATUS>::type rtk_connect_status;
TypeMap<TOPIC_RTK_POSITION>::type rtk;
TypeMap<TOPIC_RTK_POSITION_INFO>::type rtk_pos_info;
TypeMap<TOPIC_RTK_VELOCITY>::type rtk_velocity;
TypeMap<TOPIC_RTK_YAW>::type rtk_yaw;
TypeMap<TOPIC_RTK_YAW_INFO>::type rtk_yaw_info;
rtk_connect_status =
vehicle_->subscribe->getValue<TOPIC_RTK_CONNECT_STATUS>();
rtk = vehicle_->subscribe->getValue<TOPIC_RTK_POSITION>();
rtk_pos_info = vehicle_->subscribe->getValue<TOPIC_RTK_POSITION_INFO>();
rtk_velocity = vehicle_->subscribe->getValue<TOPIC_RTK_VELOCITY>();
rtk_yaw = vehicle_->subscribe->getValue<TOPIC_RTK_YAW>();
rtk_yaw_info = vehicle_->subscribe->getValue<TOPIC_RTK_YAW_INFO>();
// TODO: Check this if
if (rtk_connect_status.rtkConnected) {
// std::cout << "RTK if available "
// "(lat/long/alt/velocity_x/velocity_y/velocity_z/yaw/yaw_info/pos_info)
// ="
// << rtk.latitude << "," << rtk.longitude << "," << rtk.HFSL <<
// "," << rtk_velocity.x
// << "," << rtk_velocity.y << "," << rtk_velocity.z << "," <<
// rtk_yaw << ","
// << (uint16_t)rtk_yaw_info << "," << (uint16_t)rtk_pos_info <<
// "\n";
rtk_msg_.header.stamp = node_->now();
rtk_msg_.header.frame_id = "wgs84";
rtk_msg_.status.status = rtk_connect_status.rtkConnected;
rtk_msg_.status.service = 0; // FIXME: what is this?
rtk_msg_.latitude = rtk.latitude;
rtk_msg_.longitude = rtk.longitude;
rtk_msg_.altitude = rtk.HFSL;
// COVARIANCE UNKNOWN
// rtk_msg_.position_covariance[0] =
// rtk_msg_.position_covariance[4] =
// rtk_msg_.position_covariance[8 =
rtk_msg_.position_covariance_type = 0;
rtk_.updateData(rtk_msg_);
}
}
};
class DJISubscriptionCompass : public DJISubscription {
private:
as2::sensors::Compass compass_;
sensor_msgs::msg::MagneticField compass_msg_;
public:
DJISubscriptionCompass(as2::Node *node, Vehicle *vehicle, int frequency = 100,
bool enable_timestamp = false)
: DJISubscription("Compass", node, vehicle, frequency, enable_timestamp),
compass_("compass", node){};
protected:
void initializeTopics() override { topics_ = {TOPIC_COMPASS}; };
void onUpdate() override {
TypeMap<TOPIC_COMPASS>::type compass;
compass = vehicle_->subscribe->getValue<TOPIC_COMPASS>();
compass_msg_.header.stamp = node_->now();
compass_msg_.header.frame_id =
as2::tf::generateTfName(node_->get_namespace(), "imu");
compass_msg_.magnetic_field.x = compass.x;
compass_msg_.magnetic_field.y = compass.y;
compass_msg_.magnetic_field.z = compass.z;
compass_.updateData(compass_msg_);
}
};
// TODO: CHANGE THIS SUBSCRIPTION TO HARDWARE_SYNC
class DJISubscriptionImu : public DJISubscription {
as2::sensors::Imu imu_;
sensor_msgs::msg::Imu imu_msg_;
public:
DJISubscriptionImu(as2::Node *node, Vehicle *vehicle, int frequency = 200,
bool enable_timestamp = false)
: DJISubscription("FlightStatus", node, vehicle, frequency,
enable_timestamp),
imu_("imu", node){};
protected:
void initializeTopics() override {
topics_ = {TOPIC_ACCELERATION_BODY, TOPIC_ANGULAR_RATE_FUSIONED,
TOPIC_QUATERNION};
};
void onUpdate() override {
TypeMap<TOPIC_ACCELERATION_BODY>::type accel;
TypeMap<TOPIC_ANGULAR_RATE_FUSIONED>::type angular_rate;
TypeMap<TOPIC_QUATERNION>::type quaternion;
accel = vehicle_->subscribe->getValue<TOPIC_ACCELERATION_BODY>();
angular_rate = vehicle_->subscribe->getValue<TOPIC_ANGULAR_RATE_FUSIONED>();
quaternion = vehicle_->subscribe->getValue<TOPIC_QUATERNION>();
imu_msg_.header.stamp = node_->now();
imu_msg_.header.frame_id =
as2::tf::generateTfName(node_->get_namespace(), "imu");
imu_msg_.orientation.w = quaternion.q0;
imu_msg_.orientation.x = quaternion.q1;
imu_msg_.orientation.y = quaternion.q2;
imu_msg_.orientation.z = quaternion.q3;
imu_msg_.linear_acceleration.x = accel.x;
imu_msg_.linear_acceleration.y = accel.y;
imu_msg_.linear_acceleration.z = accel.z;
imu_msg_.angular_velocity.x = angular_rate.x;
imu_msg_.angular_velocity.y = angular_rate.y;
imu_msg_.angular_velocity.z = angular_rate.z;
// TODO: Check frame coordinates
imu_.updateData(imu_msg_);
};
};
class DJISubscriptionBattery : public DJISubscription {
as2::sensors::Battery battery_;
sensor_msgs::msg::BatteryState battery_msg_;
public:
DJISubscriptionBattery(as2::Node *node, Vehicle *vehicle, int frequency = 5,
bool enable_timestamp = false)
: DJISubscription("Battery", node, vehicle, frequency, enable_timestamp),
battery_("battery", node){};
protected:
void initializeTopics() override { topics_ = {TOPIC_BATTERY_INFO}; };
void onUpdate() override {
TypeMap<TOPIC_BATTERY_INFO>::type battery;
battery = vehicle_->subscribe->getValue<TOPIC_BATTERY_INFO>();
battery_msg_.header.stamp = node_->now();
battery_msg_.voltage = battery.voltage / 1000.0f;
battery_msg_.current = battery.current / 1000.0f;
battery_msg_.capacity = battery.capacity / 1000.0f;
battery_msg_.percentage = battery.percentage / 100.0f;
battery_.updateData(battery_msg_);
};
};
class DJISubscriptionFlightStatus : public DJISubscription {
private:
uint8_t flight_status_ = 0;
public:
DJISubscriptionFlightStatus(as2::Node *node, Vehicle *vehicle,
int frequency = 10, bool enable_timestamp = false)
: DJISubscription("FlightStatus", node, vehicle, frequency,
enable_timestamp){};
protected:
void initializeTopics() override { topics_ = {TOPIC_STATUS_FLIGHT}; };
void onUpdate() override {
TypeMap<TOPIC_STATUS_FLIGHT>::type flight_status;
flight_status = vehicle_->subscribe->getValue<TOPIC_STATUS_FLIGHT>();
if (flight_status != flight_status_) {
flight_status_ = flight_status;
RCLCPP_INFO(node_->get_logger(), "DJI Flight status changed to %d",
flight_status_);
}
flight_status_ = flight_status;
};
// TODO: integrate it better with the rest of the code
unsigned char getFlightStatus() { return flight_status_; }
};
class DJISubscriptionOdometry : public DJISubscription {
private:
as2::sensors::Sensor<nav_msgs::msg::Odometry> odom_;
nav_msgs::msg::Odometry odom_msg_;
as2::gps::GpsHandler gps_handler_;
bool is_gps_initialized_ = false;
as2::sensors::GPS gps_;
sensor_msgs::msg::NavSatFix gps_msg_;
public:
DJISubscriptionOdometry(as2::Node *node, Vehicle *vehicle, int frequency = 50,
bool enable_timestamp = false)
: DJISubscription("Odometry", node, vehicle, frequency, enable_timestamp),
odom_("odom", node),
gps_("gps", node){};
protected:
void initializeTopics() override {
topics_ = {TOPIC_ALTITUDE_FUSIONED, TOPIC_GPS_FUSED, TOPIC_QUATERNION,
TOPIC_VELOCITY};
};
void onUpdate() override {
TypeMap<TOPIC_GPS_FUSED>::type gps;
TypeMap<TOPIC_QUATERNION>::type quaternion;
TypeMap<TOPIC_VELOCITY>::type velocity;
TypeMap<TOPIC_ALTITUDE_FUSIONED>::type altitude;
gps = vehicle_->subscribe->getValue<TOPIC_GPS_FUSED>();
quaternion = vehicle_->subscribe->getValue<TOPIC_QUATERNION>();
velocity = vehicle_->subscribe->getValue<TOPIC_VELOCITY>();
altitude = vehicle_->subscribe->getValue<TOPIC_ALTITUDE_FUSIONED>();
// FIXME: This is a hack to get the GPS to work.
gps.altitude = altitude;
if (!is_gps_initialized_) {
if (gps.visibleSatelliteNumber < 4) {
RCLCPP_WARN_ONCE(node_->get_logger(), "DJI GPS not initialized");
return;
}
RCLCPP_WARN_ONCE(node_->get_logger(), "DJI GPS initialized");
gps_handler_.setOrigin(gps.latitude * 180.0 / M_PI,
gps.longitude * 180.0 / M_PI, gps.altitude);
is_gps_initialized_ = true;
}
gps_msg_.header.stamp = node_->now();
gps_msg_.header.frame_id = "wgs84";
gps_msg_.latitude = gps.latitude * 180 / M_PI;
gps_msg_.longitude = gps.longitude * 180 / M_PI;
gps_msg_.altitude = gps.altitude;
gps_msg_.position_covariance_type = 0;
gps_.updateData(gps_msg_);
double x, y, z;
gps_handler_.LatLon2Local(gps.latitude * 180.0 / M_PI,
gps.longitude * 180.0 / M_PI, gps.altitude, x, y,
z);
odom_msg_.header.stamp = node_->now();
odom_msg_.header.frame_id =
as2::tf::generateTfName(node_->get_namespace(), "odom");
odom_msg_.child_frame_id =
as2::tf::generateTfName(node_->get_namespace(), "base_link");
// DJI pose frame is in NED coordinate system, so we need to convert to ENU
odom_msg_.pose.pose.position.x = x;
odom_msg_.pose.pose.position.y = y;
odom_msg_.pose.pose.position.z = z;
odom_msg_.pose.pose.orientation.x = quaternion.q1;
odom_msg_.pose.pose.orientation.y = quaternion.q2;
odom_msg_.pose.pose.orientation.z = quaternion.q3;
odom_msg_.pose.pose.orientation.w = quaternion.q0;
double roll_frd, pitch_frd, yaw_frd;
as2::frame::quaternionToEuler(odom_msg_.pose.pose.orientation, roll_frd,
pitch_frd, yaw_frd);
// convert orientation from FRD to FLU
double roll_flu, pitch_flu, yaw_flu;
roll_flu = roll_frd;
pitch_flu = -pitch_frd;
yaw_flu = -yaw_frd + M_PI_2;
as2::frame::eulerToQuaternion(roll_flu, pitch_flu, yaw_flu,
odom_msg_.pose.pose.orientation);
Eigen::Vector3d vel_NEU(velocity.data.x, velocity.data.y, velocity.data.z);
Eigen::Vector3d vel_ENU =
Eigen::Vector3d(vel_NEU.y(), vel_NEU.x(), vel_NEU.z());
// convert ENU to FLU
auto flu_speed =
as2::frame::transform(odom_msg_.pose.pose.orientation, vel_ENU);
odom_msg_.twist.twist.linear.x = flu_speed.x();
odom_msg_.twist.twist.linear.y = flu_speed.y();
odom_msg_.twist.twist.linear.z = flu_speed.z();
odom_.updateData(odom_msg_);
};
};
#endif