diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..01d4caf --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 3.5) +project(sweet_rviz_panel) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(ament_cmake REQUIRED) +find_package(rclcpp REQUIRED) +find_package(rviz_common REQUIRED) +find_package(rviz_rendering REQUIRED) +find_package(pluginlib REQUIRED) +find_package(geometry_msgs REQUIRED) +find_package(tf2 REQUIRED) +find_package(Qt5 REQUIRED COMPONENTS Widgets) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTORCC ON) + +set(HEADERS + include/sweet_rviz_panel/sweet_rviz_panel.hpp +) + +set(SOURCES + src/sweet_rviz_panel.cpp +) + +add_library(${PROJECT_NAME} SHARED + ${SOURCES} + ${HEADERS} +) + +target_include_directories(${PROJECT_NAME} + PUBLIC + $ + $ +) + +ament_target_dependencies(${PROJECT_NAME} + rclcpp + rviz_common + rviz_rendering + pluginlib + geometry_msgs + tf2 +) + +target_link_libraries(${PROJECT_NAME} + Qt5::Widgets +) + +pluginlib_export_plugin_description_file( + rviz_common + plugin_description.xml +) + +install( + TARGETS ${PROJECT_NAME} + ARCHIVE DESTINATION lib + LIBRARY DESTINATION lib + RUNTIME DESTINATION bin +) + +install( + DIRECTORY include/ + DESTINATION include +) + +install( + FILES plugin_description.xml + DESTINATION share/${PROJECT_NAME} +) + +ament_package() \ No newline at end of file diff --git a/include/sweet_rviz_panel/sweet_rviz_panel.hpp b/include/sweet_rviz_panel/sweet_rviz_panel.hpp new file mode 100644 index 0000000..d6c2c10 --- /dev/null +++ b/include/sweet_rviz_panel/sweet_rviz_panel.hpp @@ -0,0 +1,63 @@ +#ifndef SWEET_RVIZ_PANEL_HPP +#define SWEET_RVIZ_PANEL_HPP + +#include + +#include +#include +#include +#include + +#include + +#include +#include "geometry_msgs/msg/twist.hpp" +#include "geometry_msgs/msg/pose.hpp" +#include +#include + +namespace sweet_rviz_panel +{ + +class SweetRvizPanel : public rviz_common::Panel +{ + Q_OBJECT + +public: + explicit SweetRvizPanel(QWidget *parent = nullptr); + +protected: + void onInitialize() override; + +private Q_SLOTS: + void onStart(); + void onStop(); + +private: + void poseCallback( + const geometry_msgs::msg::Pose::SharedPtr msg); + void velCmdCallback( + const geometry_msgs::msg::Twist::SharedPtr msg); + QLabel *x_label_; + QLabel *y_label_; + QLabel *yaw_label_; + QLabel *status_label_; + + QLabel *vel_cmd_label_; + QLabel *yaw_cmd_label_; + + QPushButton *start_button_; + QPushButton *stop_button_; + + rclcpp::Node::SharedPtr node_; + std::shared_ptr< + rviz_common::ros_integration::RosNodeAbstractionIface> node_ptr_; + rclcpp::Subscription::SharedPtr pose_sub_; + rclcpp::Subscription::SharedPtr twist_cmd_sub_; + + bool running_; +}; + +} // namespace sweet_rviz_panel + +#endif diff --git a/package.xml b/package.xml new file mode 100644 index 0000000..4b64451 --- /dev/null +++ b/package.xml @@ -0,0 +1,24 @@ + + + + sweet_rviz_panel + 0.0.1 + Sweet RViz2 vehicle debug panel + + sweetai + Apache-2.0 + + ament_cmake + + rclcpp + rviz_common + rviz_rendering + pluginlib + geometry_msgs + + + ament_cmake + + + + \ No newline at end of file diff --git a/plugin_description.xml b/plugin_description.xml new file mode 100644 index 0000000..10cac75 --- /dev/null +++ b/plugin_description.xml @@ -0,0 +1,10 @@ + + + + Sweet Robot RViz2 debug panel. + + + \ No newline at end of file diff --git a/src/sweet_rviz_panel.cpp b/src/sweet_rviz_panel.cpp new file mode 100644 index 0000000..a04f7b8 --- /dev/null +++ b/src/sweet_rviz_panel.cpp @@ -0,0 +1,176 @@ +#include "sweet_rviz_panel/sweet_rviz_panel.hpp" + +#include +#include +#include +#include + +#include + +namespace sweet_rviz_panel +{ + +SweetRvizPanel::SweetRvizPanel(QWidget *parent) +: rviz_common::Panel(parent), + x_label_(nullptr), + y_label_(nullptr), + yaw_label_(nullptr), + status_label_(nullptr), + start_button_(nullptr), + stop_button_(nullptr), + vel_cmd_label_(nullptr), + yaw_cmd_label_(nullptr), + running_(false) +{ + auto *main_layout = new QVBoxLayout; + // vehicle pose + auto *pose_box = new QGroupBox("Vehicle Pose"); + auto *pose_layout = new QVBoxLayout; + + x_label_ = new QLabel("X: 0.000 m"); + y_label_ = new QLabel("Y: 0.000 m"); + yaw_label_ = new QLabel("Yaw: 0.000 rad"); + + pose_layout->addWidget(x_label_); + pose_layout->addWidget(y_label_); + pose_layout->addWidget(yaw_label_); + + pose_box->setLayout(pose_layout); + // command + auto *vel_cmd_box = new QGroupBox("Command Info"); + auto *vel_cmd_layout = new QVBoxLayout; + vel_cmd_label_ = new QLabel("Velocity Command: 0.00 m/s"); + yaw_cmd_label_ = new QLabel("Yaw Command: 0.00 rad/s"); + + vel_cmd_layout->addWidget(vel_cmd_label_); + vel_cmd_layout->addWidget(yaw_cmd_label_); + vel_cmd_box->setLayout(vel_cmd_layout); + // other buttons + auto *button_layout = new QHBoxLayout; + + start_button_ = new QPushButton("Start"); + stop_button_ = new QPushButton("Stop"); + + button_layout->addWidget(start_button_); + button_layout->addWidget(stop_button_); + + status_label_ = new QLabel("Status: STOPPED"); + + main_layout->addWidget(pose_box); + main_layout->addWidget(vel_cmd_box); + main_layout->addLayout(button_layout); + main_layout->addWidget(status_label_); + main_layout->addStretch(); + + setLayout(main_layout); + + connect( + start_button_, + SIGNAL(clicked()), + this, + SLOT(onStart())); + + connect( + stop_button_, + SIGNAL(clicked()), + this, + SLOT(onStop())); +} + +void SweetRvizPanel::onInitialize() +{ + node_ = std::make_shared("sweet_rviz_panel"); + node_ptr_= getDisplayContext()->getRosNodeAbstraction().lock(); + if (!node_ptr_) { + RCLCPP_ERROR( + rclcpp::get_logger("SweetRvizPanel"), + "Failed to get RViz ROS node"); + + return; + } + auto node = node_ptr_->get_raw_node(); + + pose_sub_ = node->create_subscription< + geometry_msgs::msg::Pose>( + "/rtk_pose", + 10, + std::bind(&SweetRvizPanel::poseCallback,this,std::placeholders::_1)); + + twist_cmd_sub_ = node->create_subscription< + geometry_msgs::msg::Twist>( + "/twist_cmd_vel", + 10, + std::bind(&SweetRvizPanel::velCmdCallback,this,std::placeholders::_1)); + + running_ = true; + status_label_->setText("Status: RUNNING"); +} + +void SweetRvizPanel::poseCallback( + const geometry_msgs::msg::Pose::SharedPtr msg) +{ + if (!running_) { + return; + } + + const double x = msg->position.x; + const double y = msg->position.y; + + const auto &q = msg->orientation; + + tf2::Quaternion quaternion( + q.x, + q.y, + q.z, + q.w); + + double roll; + double pitch; + double yaw; + + tf2::Matrix3x3(quaternion).getRPY( + roll, + pitch, + yaw); + + x_label_->setText( + QString("X: %1 m").arg(x, 0, 'f', 3)); + + y_label_->setText( + QString("Y: %1 m").arg(y, 0, 'f', 3)); + + yaw_label_->setText( + QString("Yaw: %1 rad").arg(yaw, 0, 'f', 3)); +} + +void SweetRvizPanel::velCmdCallback( + const geometry_msgs::msg::Twist::SharedPtr msg) +{ + if (!running_) { + return; + } + + vel_cmd_label_->setText( + QString("Velocity Command: %1 m/s").arg(msg->linear.x, 0, 'f', 3)); + + yaw_cmd_label_->setText( + QString("Yaw Command: %1 rad/s").arg(msg->angular.z, 0, 'f', 3)); +} + +void SweetRvizPanel::onStart() +{ + running_ = true; + status_label_->setText("Status: RUNNING"); +} + +void SweetRvizPanel::onStop() +{ + running_ = false; + status_label_->setText("Status: STOPPED"); +} + +} // namespace sweet_rviz_panel + +PLUGINLIB_EXPORT_CLASS( + sweet_rviz_panel::SweetRvizPanel, + rviz_common::Panel)