RVIZ嵌入QT界面
引言:我们最近有一个解决的问题就是通过QT怎么来实时化来展示我们的机器人可视化界面,让我们的机器人实时运动来随着实时展现,我想到一种办法就是将我们的RVIZ来嵌入到我们的QT界面中来,让我们实现我们从ROS2来对我们的MIcro_ROS机器人的控制,并实时显示机器人的状态
最近在更新这个文件
第一天完成了MainWindow窗口,明天完成rviz_panel,主要实现我们MainWindow窗口的实现
如果你装载了ROS2 Humble Desktop 的话,RVIZ2应该是内置的
为了防止我们的安装是被我们安装下面的依赖,如果安装的是上面(ROS2 Humble Desktop ) 可以跳过这一步
bash
sudo apt install ros-${ROS_DISTRO}-rviz2 \
ros-${ROS_DISTRO}-rviz-common \
ros-${ROS_DISTRO}-rviz-rendering \
ros-${ROS_DISTRO}-rviz-default-plugins \
ros-${ROS_DISTRO}-robot-state-publisher \
qtbase5-dev
QT基本知识
这里可能比较枯燥,如果使用QT Creator可能比这个要简单一点
这里适合有cpp基础的人来看,主要是写这个QT框架的使用
下面讲的是这个主程序都需要什么?
这可能和我的开发习惯有关,总是先对这个大的东西来进行我们的设计
下面个人因为也是第一次开发所以就尽可能写的注释详细,大家也可以根据我的思路来设计文件
头文件依赖
cpp
// 类的声明
// QT 提供给我们的主窗口,我们要继承这个类的一些实现
class MainWindow : public QMainWindow
{
// Q_OBJECT 宏是 Qt 的元对象系统的一部分,用于支持信号和槽机制,在每一个QT程序之前都要加上
Q_OBJECT
public:
// Qwidget所有QT UI组件的基类,下面的UI组件都需要继承这个parent的对象
explicit MainWindow(rclcpp::Node::share_ptr node,Qwidget*parent=nullptr);
~MainWindow()override;
// rviz初始化
initalizeRviz();
// Qt 提供的槽函数,下面是槽函数的声明,就是我们在ROS中的subscription接受者,只是在单个线程中的这个函数
public slot:
// 归零函数
void OnGoHome();
// 调节各个关节的角度的函数,需要传入各个关节的位置,所以要传入应该数组
void onJointAnglesChanged(const std::vector<double>& angles_deg);
// 准备位函数-预留给我们的抓取,visual pose,也可以使用检测我们的通信是否正常
void OnGoReady();
// 下面是不希望让用户修改的功能,相当于我们的config,个人的设计思路,一般把config文件放在我们的private下面
private:
// 设计我们的界面的API函数
void SetUpUi();
// 发布我们的关节的状态
void publishJointStates();
// 下面是我们需要用到的成员函数,ROS编程中都有介绍我就不介绍了
rclcpp::node::share_ptr node_;
rclcpp::publisher<sensor_msgs::msgs::msgs::Jointstate>share_ptr Joint_pub_;
rclcpp::TimerBase::SharedPtr timer_;
// UI 组件
// rviz面板
RvizPanel* rviz_panel_;
// 关节控制面板
JointControlPanel* joint_panel_;
// 当前关节角度(弧度)
std::vector<double> current_joint_angles_;
// 关节名称
std::vector<std::string> joint_names_;
};
下面我们就对我们的功能来进行实现了
cpp
// 构造函数,初始化成员
mainwindow::mainwindow(rclcpp::Node::Share_ptr node,QWidget *parent):
QMainWindow(parent)
,node_(node)
,rviz_panel(nullptr)
,joint_panel(nullptr)
{
// 初始化关节数据
joint_names_ = {"joint_1", "joint_2", "joint_3", "joint_4", "joint_5", "joint_6"};
current_joint_angles_.resize(6, 0.0);
// 发布关节的位置
Joint_pub_->node->create_publisher<sensor_msgs::msgs::msgs::JointState>("joint_states",10);
// 定时发布关节位置,每50ms调用一次这个发布者
timer_ =node->create_wall_timer(
std::chrono::milliseconds(50),
std::bind(&MainWindow::publishJointState,this));
setUpUi();
}
// 析构函数的实现
~MainWindow(){
// 将定时器对象释放
if(timer_){
timer_->cancel();
}
}
// 回调函数的实现
void MainWindow::publisherJointState(){
// 初始化消息类型
auto msg =sensor_msgs::msg::JointState();
// 消息的头部分
msg.header.stamp=node->get_clock()->now();
// 消息的名称,我们开始初始化消息
msg.name=joint_names_;
msg.position=current_joint_angles_;
// 通过Joint_pub_将消息发出来
joint_pub_->publish(msg);
}
// 初始化U组件
void MainWindow::setUpUi(){
//可视化界面
// 中心部件
QWidget* central = new QWidget(this);
setCentralWidget(central);
QHBoxLayout * main_layout=new(central):
// 创建面板
QSplitter * splitter =new QSplitter(Qt::Horizontal,central);
// 左侧RVIZ控制面板,先添加的是在左边,后添加的是在右边
rviz_panel_ =new RvizPanel(node_,sqlitter);
rviz_panel_->setMinmumWidth(600);
// 将这个
splitter->addWidget(rviz_panel_);
// 右侧控制面板
joint_panel_ = new JointControlPanel(6,Splitter);
joint_panel_ ->setMinimumWidth(250);
joint_panel_->setMaximumWidth(400);
// 设置关节限位
std::vector<std::pair<double,double>>limits={
{-180, 180}, // Joint 1: 底座
{-90, 90}, // Joint 2: 肩部
{-135, 135}, // Joint 3: 肘部
{-180, 180}, // Joint 4: 腕部旋转
{-90, 90}, // Joint 5: 腕部俯仰
{-180, 180} // Joint 6: 末端
}
// 设置关节的限位
for (int i = 0; i < 6; i++) {
joint_panel_->setJointName(i, joint_names_[i]);
joint_panel_->setJointLimits(i, limits[i].first, limits[i].second);
}
splitter->setStretchFactor(0,3);
splitter->setStretchFactor(1,1);
main_layout->addWidget(splitter);
// 控制部分
//连接信号
connect(joint_panel_,&JointControlPanel::jointAngleschanged,
this,&MainWindow::onJointAngleschanged);
// 回零点信号和槽进行处理
connect(joint_panel_,&JointControlPanel::jointAngleschanged,this,&MainWindow::onGoHome);
// 准备信号合槽进行处理
connect(joint_panel_,&JointControlPanel::jointAngleschanged,this,&MainWindow::onGoHome);
// 菜单部分,提供退出按键
QMenu * file_menu =menuBar()->addMenu("文件");
QAction * quit_action =file_menu=file_menu->addAction(
// 将菜单上的退出槽给我们的退出信号关联
connect(quit_action,&QAtion::triggered,this,&QMainWindow::close);
statusBar()->showMessage("就绪 - 等待 robot_description");
}
// 连接信号的处理函数
// 改变关节角度的信号函数
void MainWindow::JointAngleschanged(const std::vector<double>&angles_deg){
// 度转弧度
for (size_t i = 0; i < angles_deg.size() && i < current_joint_angles_.size(); i++) {
current_joint_angles_[i] = angles_deg[i] * M_PI / 180.0;
}
// 更新状态栏
QString status ="关节角度";
for(size_t i=0;i<angles_deg.size();i++){
// 这里类似于printf %1 表示第一个参数的值arg(i+1),%2表示第二个参数的值(angles_deg[i])
//.arg(value, fieldWidth, format, precision)
// fieldWidth = 0 最小宽度 0 不填充
// format = 'f' 定点小数点格式
// precision = 1 小数点后1位
status+=QString"J%1=%2° "。arg(i+1).arg(angles_deg[i],0,'f',1);
}
}
// 回零位的函数
void MainWindow::onGoHome(){
std::vector<double> home(6,0.0);
joint_panel->setJointAngles(home);
}
// 回准备位的函数
void MainWindow::onGoReady(){
std::vector<double> ready = {0, -45, 90, 0, 45, 0};
joint_panel_->setJointAngles(ready);
}
void MainWindow::initalizeRviz(){
if(rviz_panel_){
rviz_panel_->initalize();
// 添加RVIZ可视化组件
rviz_panel_->addGrid();
// 机器人模型
rviz_panel_->addRobotModel();
// TF树
rviz_panel_->addTF();
statusBar()->showMessage("Rviz init success");
}
}