這幾天為了解決這些問題一直在發愁,今天晚上終於成功了,前后算起來花了將近4天的時間,現在做一個總結。
想要在QT中制作一個上位機實現三個主要功能:
使用UDP協議接收外部發過來的數據
對接受到的UDP數據進行處理,得到想要的數據
將處理好的數據按照ROS的格式和要求使用TCP協議轉發出去
在此過程中遇到兩個主要的問題:
問題一: 成員變量是類對象,且該類對象的構造函數是含有參數的
UDP部分的功能已經實現,主要的實現程序如下:
#include "widget.h"
#include "ui_widget.h"
#include <QHostAddress>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//分配空間,指定父對象
udpSocket = new QUdpSocket(this);
//綁定
udpSocket->bind(10022);
setWindowTitle("本端口為:10022");
//當對方成功發送數據過來
//自動觸發 readyRead()
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dealMsg()));
}
void Widget::dealMsg()
{
//讀取對方發送的內容
char buf[1024] = {0};
QHostAddress cliAddr; //對方IP
quint16 port; //對方端口
qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
if(len > 0)
{
//格式化
QString str = QString ("%1").arg(buf); //獲取的數據
//給編輯區設置內容
ui->textEdit->append(str);
}
}
Widget::~Widget()
{
delete ui;
}
而ros與windows通信的例程可以參考我之前寫的一篇博客:《基於rosserial_windows實現ROS與windows通信(親測可行)》
其中給的代碼例程如下:
/* rosserial_hello_world.cpp : Example of sending command velocities from Windows using rosserial
*/
#include "stdafx.h"
#include <string>
#include <stdio.h>
#include "ros.h"
#include <geometry_msgs/Twist.h>
#include <windows.h>
using std::string;
int _tmain (int argc, _TCHAR * argv[])
{
//創建節點句柄
ros::NodeHandle nh;
//ROS所在主機IP
char *ros_master = "1.2.3.4";
printf ("Connecting to server at %s\n", ros_master);
nh.initNode (ros_master);
printf ("Advertising cmd_vel message\n");//發布cmd_vel信息
//初始化消息類型
geometry_msgs::Twist twist_msg;
//創建一個發布者發布話題
ros::Publisher cmd_vel_pub ("cmd_vel", &twist_msg);
nh.advertise (cmd_vel_pub);
printf ("Go robot go!\n");
//發布的內容
while (1)
{
twist_msg.linear.x = 5.1;
twist_msg.linear.y = 0;
twist_msg.linear.z = 0;
twist_msg.angular.x = 0;
twist_msg.angular.y = 0;
twist_msg.angular.z = -1.8;
cmd_vel_pub.publish (&twist_msg);
//循環等待回調函數
nh.spinOnce ();
//休眠100ms
Sleep (100);
}
printf ("All done!\n");
return 0;
}
首先需要在QT中包含ros生成的頭文件庫,所需要包含的東西全部寫到.pro文件當中:
//因為用到網絡編程,所以添加network
QT += core gui network
//將幾個頭文件和.cpp文件添加到工程當中
SOURCES += main.cpp\
widget.cpp \
ros_lib/duration.cpp \
ros_lib/time.cpp \
ros_lib/WindowsSocket.cpp
HEADERS += widget.h \
ros_lib/ros.h \
ros_lib/WindowsSocket.h
//這一步是添加ros_lib的路徑,其中是頭文件庫
INCLUDEPATH +=F:\3Code\QtCode\UDP\ros_lib
//添加依賴,相當於VS當中的#pragma comment(lib, "ws2_32.lib")
LIBS += -lpthread libwsock32 libws2_32
LIBS += -lWs2_32
//支持C++11
CONFIG += c++11
如何將UDP和ROS中的TCP客戶端兩段代碼寫到一起呢?
難點在於在構造函數中的這一部分,如果我們這樣寫的話:
{
ui->setupUi(this);
//給ros發送數據的初始化
ros::NodeHandle nh;
char *ros_master = "192.168.1.3";
nh.initNode(ros_master);
geometry_msgs::Twist twist_msg;
ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);
nh.advertise(cmd_vel_pub);
//賦一個值
twist_msg.linear.x = 5.1;
twist_msg.linear.y = 0;
twist_msg.linear.z = 0;
twist_msg.angular.x = 0;
twist_msg.angular.y = 0;
twist_msg.angular.z = -1.9;
//分配空間,指定父對象
udpSocket = new QUdpSocket(this);
//綁定
udpSocket->bind(10022);
setWindowTitle("本端口為:10022");
//當對方成功發送數據過來
//自動觸發 readyRead()
connect(udpSocket,SIGNAL(readyRead()),this,SLOT(dealMsg()));
}
//槽函數,QT中的槽函數在這里沒有辦法傳遞參數
void Widget::dealMsg()
{
//讀取對方發送的內容
char buf[1024] = {0};
QHostAddress cliAddr; //對方IP
quint16 port; //對方端口
qint64 len = udpSocket->readDatagram(buf,sizeof(buf),&cliAddr,&port);
if(len > 0)
{
//格式化
QString str = QString ("%1").arg(buf); //獲取的數據
//給編輯區設置內容
ui->textEdit->append(str);
//發送部分
cmd_vel_pub.publish(&twist_msg);
nh.spinOnce();
}
}
如果QT的槽函數傳遞參數了的話,需要信號函數也有參數,且槽函數的參數類型需要和信號函數的參數類型一致,並且槽函數的參數個數不能大於信號函數的參數個數。但是readyRead()是一個沒有參數的信號函數,那么槽函數中的這兩句代碼就沒有辦法使用:
//發送部分
cmd_vel_pub.publish(&twist_msg);
nh.spinOnce();
必須把cmd_vel_pub,twist_msg,nh傳遞進來,他們分別是三個類的實例對象。如果沒有辦法讓槽函數傳遞參數進來,那么想到的辦法就是將以上的三個對象變為Widget類的成員變量,只不過是三個類成員變量而已,那么槽函數就直接可以使用這三個對象啦,哈哈!
但是困難接踵而來,可以在private中這樣寫:
private:
ros::NodeHandle nh;
geometry_msgs::Twist twist_msg;
ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);
但是ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);這句死活不行啊,查看Publisher類發現,它的構造函數只有一個有參的構造函數,如下:
Publisher(const char * topic_name, Msg * msg, int endpoint = rosserial_msgs::TopicInfo::ID_PUBLISHER) :
topic_(topic_name),
msg_(msg),
endpoint_(endpoint) {};
這種數據成員是對象,並且這個對象只有含參數的構造函數,沒有無參數的構造函數,是如何聲明的呢?
如果我們有一個類成員,它本身是一個類或者是一個結構,而且這個成員它只有一個帶參數的構造函數,而沒有默認構造函數,這時要對這個類成員進行初始化,就必須調用這個類成員的帶參數的構造函數,使用初始化裂變對齊進行初始化,如果沒有初始化列表,那么他將無法完成第一步,就會報錯。
具體可以參考這兩篇博客:c++中在一個類中定義另一個帶參數構造函數的類的對象、C++必須使用【初始化列表】初始化數據成員的三種情況
那么在我的程序中,就應該這么寫:
// widget.h
private:
ros::NodeHandle nh;
geometry_msgs::Twist twist_msg;
ros::Publisher cmd_vel_pub("cmd_vel", &twist_msg);
// widget.cpp
Widget::Widget(QWidget *parent) :
QWidget(parent),
cmd_vel_pub("cmd_vel", &twist_msg),
ui(new Ui::Widget)
這樣一來,三個對象就都可以在槽函數中自由的使用啦!
問題二:發送給ROS時出現10053的錯誤
通過網上搜索,10053的錯誤可能引起的原因如下:
10053錯誤:1、可能軟件的其它地方關閉了socket;
2、可能對端已關閉了連接。
那么具體是是么原因引起的呢?使用抓包軟件進行抓包處理,分析一波:
這個是正常進行TCP發送數據時抓到的包:
可以看出正常的情況下,經過TCP的三次握手之后,就可以進行數據的傳輸。
那么出現10053的情況下進行抓包的結果如下:
我們可以看出,雖然握手成功,但是1s之后,ROS這邊TCP服務器就關閉了套接字,將FIN置為1。
看來問題是出現在了ROS這邊,如果ROS和windows獎勵起TCP連接后,如果1s內收不到數據,那么服務器就會將套接字關閉。接下來通過查看ROS中rosserial_sever包中socket_node源碼,找到了修改的辦法。沒有源碼的可以將之前安裝的rosserial_sever包卸載掉,再下載源碼進行編譯。
我們找到源碼中session.h這個頭文件,將 attempt_interval_ = boost::posix_time::milliseconds(1000);進行修改,可以將1000改的大一點,例如10000,這樣只要間隔10s內可以收到數據,服務器就不會關閉套接字。
修改過之后記得要catkin_make一下!
————————————————
版權聲明:本文為CSDN博主「編程芝士」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/zzu_seu/article/details/95379087
