本博客是博主在学习古月学院的ros+qt课程中的课程笔记,欢迎共同学习
目录
1.qt多线程
在ros节点中,需要循环调用spinOnce()函数,在while(ros::ok())里面
如果把这个死循环放入主进程,界面会直接卡死,因此需要使用多线程机制
开辟一个新的线程来执行一些比较费时的操作,就类似js里面的异步
QNode继承于QThread,通过重写run()方法,在其中实现耗时的功能,比如while循环之类,这些代码的运行不会影响界面主线程的显示
举例:将以往放在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<std_msgs::String>("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,在下方放置线速度和角速度的控制条,做好排版
更改命名,在ui界面选中某一个button后,在shortcut这一栏输入对应的字母后,即成为快捷键
设置拖动条的最大值最小值,数字显示的最小值,防止因数字长度变化影响布局
添加槽函数使数字显示和拖动条关联
先写connet,然后在hpp里面定义槽函数,在cpp里面实现槽函数(详见qt基础的博客)
接下来将各个按钮与方向对应起来
QPushButton* btn=qobject_cast<QPushButton*> (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);
参考资料