【Qt提高】Qt+ROS界面软件开发(一)

本博客是博主在学习古月学院的ros+qt课程中的课程笔记,欢迎共同学习

目录

1.qt多线程

2.在Qt中创建publisher和subscriber

3.     键盘实现控制

4.速度仪表盘实现


1.qt多线程

在ros节点中,需要循环调用spinOnce()函数,在while(ros::ok())里面

如果把这个死循环放入主进程,界面会直接卡死,因此需要使用多线程机制

开辟一个新的线程来执行一些比较费时的操作,就类似js里面的异步

QNode继承于QThread,通过重写run()方法,在其中实现耗时的功能,比如while循环之类,这些代码的运行不会影响界面主线程的显示

【Qt提高】Qt+ROS界面软件开发(一)_第1张图片

举例:将以往放在ros节点main函数中的内容放在run()中,在qt+ros软件中main函数中不包括这些

void QNode::run(){
    ros::Rate loop_rate(1);
    while(ros::ok()){
        std::msgs::String strmsg;
        std::stringstream ss;
        ss<< "hello qt_ros";
        strmsg.data = ss.str();
        publisher_1.publish(strmsg);
        log(Info,std::string("pub:")+strmsg.data);

        ros::spinOnce();
        loop_rate.sleep();
      
    }
    std::cout<<"shut down";
    Q_EMIT rosShutdown();
}

2.在Qt中创建publisher和subscriber

在QNode.hpp里面定义publisher 或 subscriber对象+对应的回调函数定义

在QNode的初始化函数中绑定话题

chatter_publisher = n.advertise("chatter", 1000);
chatter_sub=n.subscribe("chatter",1000,&QNode::chatter_callback,this);

在QNode中实现callback函数

void QNode::chatter_callback(const std_msgs::String &msg)
{
    log(Info,"I recive"+msg.data);
}

在需要的时候pub对应话题的信息

3.键盘实现控制

首先,在ui界面中加入八个方向对应的pushbutton,并在中间放置一个是否是全向轮的checkbox,在下方放置线速度和角速度的控制条,做好排版

【Qt提高】Qt+ROS界面软件开发(一)_第2张图片

更改命名,在ui界面选中某一个button后,在shortcut这一栏输入对应的字母后,即成为快捷键

【Qt提高】Qt+ROS界面软件开发(一)_第3张图片

设置拖动条的最大值最小值,数字显示的最小值,防止因数字长度变化影响布局

添加槽函数使数字显示和拖动条关联

先写connet,然后在hpp里面定义槽函数,在cpp里面实现槽函数(详见qt基础的博客)

接下来将各个按钮与方向对应起来

QPushButton* btn=qobject_cast (sender());
char k=btn->text().toStdString()[0];

k的值就是输入的键盘字符

判断车是否是全向轮

接下来将方向和按键一一对应起来

//非全向轮(需要旋转方向)
      {'i', {1, 0, 0, 0}},
      {'o', {1, 0, 0, -1}},
      {'j', {0, 0, 0, 1}},
      {'l', {0, 0, 0, -1}},
      {'u', {1, 0, 0, 1}},
      {',', {-1, 0, 0, 0}},
      {'.', {-1, 0, 0, 1}},
      {'m', {-1, 0, 0, -1}},
//全向轮 
      {'O', {1, -1, 0, 0}},
      {'I', {1, 0, 0, 0}},
      {'J', {0, 1, 0, 0}},
      {'L', {0, -1, 0, 0}},
      {'U', {1, 1, 0, 0}},
      {'<', {-1, 0, 0, 0}},
      {'>', {-1, -1, 0, 0}},
      {'M', {-1, 1, 0, 0}},
//上下      
      {'t', {0, 0, 1, 0}},
      {'b', {0, 0, -1, 0}},
//不做移动   
      {'k', {0, 0, 0, 0}},
      {'K', {0, 0, 0, 0}}

按照设定的线速度和速度及对应方向给出twist指令

//moveBinding是上面给出的方向矩阵
   int x = moveBindings[key][0];
    int y = moveBindings[key][1];
    int z = moveBindings[key][2];
    int th = moveBindings[key][3];//转向

    geometry_msgs::Twist twist;
    twist.linear.x=x*linear;
    twist.linear.y=y*linear;
    twist.linear.z=z*linear;

    twist.angular.x=0;
    twist.angular.y=0;
    twist.angular.z=th*angular;

    cmd_vel_pub.publish(twist);

通过rostopic echo cmd_vel(速度指令话题名称) 可以看到速度指令已经发布

4.速度仪表盘实现

信号与槽机制是qt的核心,它们可以携带任意数量和任意类型的参数,不会像回调函数那样造成核心转储.

什么是核心转储?
核心转储就是在linux或是类unix系统中,当一个进程发生错误或是收到信号要终止时,系统会将进程执行时的内存内容写入一个core文件,以作为调试之用,这就是核心转储(core dumps)。

再说说信号signal这个东西

所有从QObject或其子类( 例如 Qwidget) 派生的类都能够包含信号和槽。当对象改变其状态时,信号就由该对象发射 (emit) 出去,这就是对象所要做的全部事情,它不知道另一端是谁在接收这个信号

信号的声明是在头文件中进行的,QT 的 signals 关键字指出进入了信号声明区,随后即可声明自己的信号。

需要的时候利用emit关键字发出信号,connect函数的第二个参数是信号,它把信号和槽连接起来,需要注意的是信号和槽并不是一对一的关系.

在速度仪表盘应用中,逻辑就是从里程计数据中读出xy方向上的速度值,通过信号发射出去,对应的槽函数负责将他们展示在仪表盘上

step1:订阅nav_mags/Odometry 类型的里程计信息

odom_sub=n.subscribe("raw_odom",1000,&QNode::odom_callback,this);

 step2:回调函数发出速度信号

创建自定义信号:

void speed_vel(float,float);

odom_callback:

 emit speed_vel(msg.twist.twist.linear.x,msg.twist.twist.linear.y);

step3:连接信号和槽,槽函数更新仪表盘数据

connect(&qnode,SIGNAL(speed_vel(float,float)),this,SLOT(slot_update_dashboard(float,float)));

槽函数:

    ui.label_dir_x->setText(x>0?"正向":"反向");
    ui.label_dir_y->setText(y>0?"正向":"反向");
//仪表盘没有负向,故此处用文字显示方向,仪表盘的数据则显示的是速度的绝对值
    speed_x_dashBoard->setValue(abs(x)*100);
    speed_y_dashBoard->setValue(abs(y)*100);

 

参考资料

https://blog.csdn.net/zong596568821xp/article/details/78917689?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242

你可能感兴趣的:(日常自用,c++,其他)