本节将会介绍如何实现示波器上位机的网络数据共享,使得用户可以通过PC机、平板电脑等设备远程查看波形测量结果并配置示波器参数。下文将介绍如何在旭日X3派的上位机界面中添加TCP服务器,实现上述功能;并将客户端和服务端融合,
#include <QTcpSocket>
#include <QTcpServer>
在同一个界面上实现远程测量显示功能和本地服务器功能切换。
Part 1:服务器框架设计
本小节将会介绍如何编写服务器框架,使得客户端能够实连接到此上位机,并实现数据回环测试。
Step 1:在mainwindow.h中添加Socket网络库,并建立Socket对象指针(私有变量)
#include <QTcpSocket>
#include <QTcpServer>
QTcpServer *tcpServer=nullptr;
QList<QTcpSocket*> tcpSocket;
Step 2:为mainwindow.c添加TCP服务端打开函数:
bool MainWindow::openSocketServer()//Open SocketServer
{
if(tcpServer)
{
;//Doing Nothing if Server Object was settled
}
else
{
tcpServer=new QTcpServer(this);
connect(tcpServer,&QTcpServer::newConnection,this,&MainWindow::on_TCPserver_Connect);
}
if(tcpServer==nullptr)return false;
if(tcpServer->isListening())
{
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor++)
(*itor)->disconnectFromHost();
tcpServer->close();
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor=tcpSocket.erase(itor));
}
return tcpServer->listen(QHostAddress::Any ,11401);
}
本段中主要根据tcpServer指针状态创建了一个监听11401端口的TCP服务端,如果先前已经创建了TCP服务端,则把服务端的连接关闭后再重新创建。
Step 3:添加连接建立回调函数
void MainWindow::on_TCPserver_Connect(void)//TCP Connection Enstablish slot
{
printf("%s",QString("Got Connection\r\n").toLocal8Bit().data());
QTcpSocket *socket=tcpServer->nextPendingConnection();
tcpSocket.append(socket); //Append Socket Object to List
connect(socket,&QTcpSocket::readyRead,this,&MainWindow::on_TCPserver_Read);
connect(socket,&QTcpSocket::disconnected,this,&MainWindow::on_TCPserver_Disconnect);
}
本段代码主要在有TCP客户端连接服务器时执行,服务器会将此设备的套接字(Socket)对象加入到tcpSocket链表中,并针对此套接字对象链接对应的数据接收槽函数(当客户端发来数据时会触发此函数)。
Step 4:添加断开连接回调函数:
void MainWindow::on_TCPserver_Disconnect(void)//TCP Disconnect Opt
{
int cnt=0;
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();)
{
QTcpSocket *socket=(QTcpSocket*)*itor;
if(socket->state()==QTcpSocket::ConnectedState)
itor++;
else
itor=tcpSocket.erase(itor);
cnt++;
}
}
本段代码主要在有TCP客户端断开服务器连接时执行,服务器会将此设备的套接字对象从tcpSocket链表中移除,不再对其数据进行处理。
Step 5:添加接收回调函数
void MainWindow::on_TCPserver_Read(void)//TCP Server Recv Data
{
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor++)
{
QTcpSocket *socket=(QTcpSocket*)*itor;
QByteArray RxBuff=socket->readAll();
if(RxBuff.length()==0)continue;
socket->write(RxBuff);//Loopback
QString RxStr=QString::fromLocal8Bit(RxBuff);
if(tcpSocket.length()>1)
RxStr="<"+socket->peerAddress().toString()+":"+QString::number(socket->peerPort())+">"+RxStr+"\n";
qDebug()<<RxStr;
int spilt=RxStr.indexOf(".");
int end=RxStr.indexOf(";");
if((spilt==-1)||(end==-1))continue;//if Not A Currect CMD, PASS this Node
qDebug()<<"GOT CMD";
}
}
本段代码主要在有TCP客户端向服务器发送数据时执行,服务器会遍历所有连接的Socket对象,处理发来的数据。
本段代码主要执行了2部分操作:数据回环、命令解析,服务端会将收到的数据原样返回给客户端,并且通过调试窗口输出出来,如果接收到命令格式的数据,服务端还会从调试窗口输出“GOT CMD”字样。
Step 6:为槽函数添加声明
bool openSocketServer();//Open SocketServer
void on_TCPserver_Connect(void);//TCP Connection Enstablish slot
void on_TCPserver_Read(void);//TCP Server Recv Data
void on_TCPserver_Disconnect(void);//TCP Disconnect Opt
Step 7:构造函数添加初始化代码
if(!openSocketServer())
{
delete tcpServer;
tcpServer=nullptr;
qDebug()<<"TCP Server Start Failed!";
QMessageBox::warning(this,tr("TCP Server Start Failed!"),tr("Cannot Start OSC server, Port was Listen aready."),QMessageBox::Close);
}
本段代码主要调用了先前的服务器开启代码,如果开启失败则会删除服务器对象,并弹窗提示。
Step 8:构析函数添加
MainWindow::~MainWindow()
{
delete ui;
if(myserial)
{
if(myserial->isOpen()) myserial->close();
delete myserial;
}
if(tcpServer)
{
if(tcpServer->isListening()) tcpServer->close();
delete tcpServer;
}
}
Step 9:测试验证
经过上述代码编写,已经初步实现一个可以实现数据回环的TCP服务端,可以使用TCP测试工具进行测试(工具链接见附录)。
先使用测试工具建立一个TCP客户端目标端口填写为11401,IP地址根据旭日X3派实际情况填写。
配置发送缓冲区为“非16进制模式”(上侧不勾选),并点击连接按钮。
此时发送数据可以看到右侧屏幕下侧的接收数据结果
Part 2:添加数据转发代码
接下来将从串口数据接收回调和TCP接收回调函数中分别添加代码,实现数据转发。
Step 1:添加串口数据广播代码
void MainWindow::on_SerialRecv(void)
{
static QByteArray RecvBuff;
if(myserial->isOpen())
{
QByteArray QBArecv =myserial->readAll();
RecvBuff.append(QBArecv);
char SyncArray[]={0x53,0x59,0x4E,0x43,0x00,0xFF,0x00,0xFF};
QByteArray Sync=QByteArray(SyncArray,8);
if(RecvBuff.indexOf(Sync)!=-1)
{
QByteArray Packed=RecvBuff.left(RecvBuff.indexOf(Sync)+8);
RecvBuff=RecvBuff.mid(RecvBuff.indexOf(Sync)+8);
//省略了绘图代码,实际上需要保留
if(tcpServer)
{
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor++)
{
QTcpSocket *socket=(QTcpSocket*)*itor;
socket->write(QBArecv);//TransMit
}
}
}
}
}
Step 2:修改TCP读取回调
void MainWindow::on_TCPserver_Read(void)//TCP Server Recv Data
{
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor++)
{
QTcpSocket *socket=(QTcpSocket*)*itor;
QByteArray RxBuff=socket->readAll();
if(RxBuff.length()==0)continue;
//省略了回环,按需保留
int spilt=RxStr.indexOf(".");
int end=RxStr.indexOf(";");
if((spilt==-1)||(end==-1))continue;//if Not A Currect CMD, PASS this Node
qDebug()<<"GOT CMD";
if(myserial->isOpen())myserial->write(RxBuff);
}
}
Step 3:数据接收测试
将TCP测试工具的接收窗口改为“16进制显示”(下侧勾选16进制)
连接服务后,可以看到有源源不断的数据从服务端发出;发送字符串形式的控制命令,可以看到右侧调试输出可以正常识别运行模式。
Part 3:添加客户端功能
Step 1:修改界面
按照下图修改界面,添加网络配置框,以及最下侧的“Remote Mode”复选框,并且修改对象名如图右所示。
Step 2:修改mainwindow.h,添加客户端回调函数及对象指针。
void on_TCP_Connect();
void on_TCP_Read();
void on_TCP_Disconnect();
QTcpSocket *tcpClient=nullptr;
Step 3:为“Remote Mode”复选框添加stateChanged(int)回调函数
void MainWindow::on_checkBoxLocal_stateChanged(int arg1)
{
ui->groupBoxPortSel->setVisible(!arg1);
ui->groupBoxNetSel->setVisible(arg1);
if(arg1)
{
if(myserial)if(myserial->isOpen())myserial->close();
if(tcpClient)//Opened to close
{
;
}
else//Closed to open
{
ui->textEditIP->setText("localhost");
ui->textEditPort->setText("11401");
tcpClient=new QTcpSocket(this);
connect(tcpClient,&QTcpSocket::connected,this,&MainWindow::on_TCP_Connect);
connect(tcpClient,&QTcpSocket::readyRead,this,&MainWindow::on_TCP_Read);
}
}
else
{
if(tcpClient)
{
disconnect(tcpClient,&QTcpSocket::connected,this,&MainWindow::on_TCP_Connect);
disconnect(tcpClient,&QTcpSocket::readyRead,this,&MainWindow::on_TCP_Read);
disconnect(tcpClient,&QTcpSocket::disconnected,this,&MainWindow::on_TCP_Disconnect);
if(tcpClient->isOpen())
{
tcpClient->disconnectFromHost();
tcpClient->close();
}
ui->pushButtonOpenConnect->setText("Connect");
delete tcpClient;
tcpClient=nullptr;
}
}
}
此段代码主要是根据模式,选择性开启TCP Client。远程模式会自动关闭串口,打开TCP Client;非远程模式下会自动关闭TCP Client。
Step 4:为连接按钮添加回调函数
void MainWindow::on_pushButtonOpenConnect_clicked()
{
if(ui->checkBoxLocal->isChecked())
{
if(tcpClient==nullptr)return;
if(tcpClient->isOpen())
{
tcpClient->disconnectFromHost();
tcpClient->close();
ui->pushButtonOpenConnect->setText("Connect");
}
else
{
QString ip=ui->textEditIP->toPlainText();
int port=ui->textEditPort->toPlainText().toInt();
tcpClient->connectToHost(ip,quint16(port));
}
}
}
Step 5:添加TCP Client连接、断开连接回调函数
void MainWindow::on_TCP_Connect(void)
{
printf("%s",QString("Connectted\r\n").toLocal8Bit().data());
connect(tcpClient,&QTcpSocket::disconnected,this,&MainWindow::on_TCP_Disconnect);
ui->pushButtonOpenConnect->setText("DisConnect");
}
void MainWindow::on_TCP_Disconnect(void)
{
printf("%s",QString("Disconnect\r\n").toLocal8Bit().data());
tcpClient->close();
if(ui->pushButtonOpenConnect)ui->pushButtonOpenConnect->setText("Connect");
}
Step 6:参照串口函数添加读取与绘图函数
void MainWindow::on_TCP_Read(void)
{
static QByteArray RecvBuff;
QByteArray QBArecv=tcpClient->readAll();
{
RecvBuff.append(QBArecv);
char SyncArray[]={0x53,0x59,0x4E,0x43,0x00,0xFF,0x00,0xFF};
QByteArray Sync=QByteArray(SyncArray,8);
if(RecvBuff.indexOf(Sync)!=-1)
{
QByteArray Packed=RecvBuff.left(RecvBuff.indexOf(Sync)+8);
RecvBuff=RecvBuff.mid(RecvBuff.indexOf(Sync)+8);
QBArecv=Packed;
QString Recv="";
if(1)
{
QString Temp;
int cnt=0;
QList<QPointF> points;
foreach(QChar dat,QBArecv)
{
Recv+=Temp.sprintf("%02X ",(unsigned int)dat.unicode());
if(cnt<(QBArecv.length()-8))points.append(QPointF(cnt++,(unsigned int)dat.unicode()));
}
series->replace(points);
chart->axisX()->setRange(0,cnt-1);
}
else Recv+=QString::fromLocal8Bit(QBArecv);
qDebug()<<Recv;
qDebug()<<"Lenth="<<Packed.length();
if(tcpServer)
{
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor++)
{
QTcpSocket *socket=(QTcpSocket*)*itor;
socket->write(QBArecv);//TransMit
}
}
}
}
}
Step 7:构造构析函数添加(目前代码的最后一行):
为构造函数和构析函数分别加入下列代码,实现TCP客户端初始化。
on_checkBoxLocal_stateChanged(0);
Step 8:波形同步显示测试
编译代码并运行,可以得到具有远程查看功能的示波器界面,分别建立2个示波器界面:
第一个按照通常的使用模式,通过串口连接示波器下位机即可。
第二个界面,勾选“Remote Mode” 复选框,并点击“Connect”按钮便可实时获取波形数据。
Part 4:完善界面功能
Step 1:修改下拉框回调函数,添加TCP命令发送代码
void MainWindow::on_comboBoxBuffer_currentIndexChanged(const QString &arg1)
{
QString Tx;
Tx.sprintf("function:WindowLenth.%d;\r\n", (int)arg1.toInt());
if(myserial->isOpen())myserial->write(Tx.toLocal8Bit());
if(tcpClient)if(tcpClient->isOpen())tcpClient->write(Tx.toLocal8Bit());
}
void MainWindow::on_comboBoxSampleRate_currentIndexChanged(int index)
{
int SampleMap[]={500,1000,2500,5000,10000,25000,50000,100000,250000,500000,1000000};
QString Tx;
Tx.sprintf("function:SimpleRate.%d;\r\n", SampleMap[index]);
if(myserial->isOpen())myserial->write(Tx.toLocal8Bit());
if(tcpClient)if(tcpClient->isOpen())tcpClient->write(Tx.toLocal8Bit());
}
Step 2:修改服务端命令解析代码
void MainWindow::on_TCPserver_Read(void)//TCP Server Recv Data
{
for(auto itor=tcpSocket.begin();itor!=tcpSocket.end();itor++)
{
QTcpSocket *socket=(QTcpSocket*)*itor;
QByteArray RxBuff=socket->readAll();
if(RxBuff.length()==0)continue;
socket->write(RxBuff);//Loopback
QString RxStr=QString::fromLocal8Bit(RxBuff);
if(tcpSocket.length()>1)
RxStr="<"+socket->peerAddress().toString()+":"+QString::number(socket->peerPort())+">"+RxStr+"\n";
qDebug()<<RxStr;
int spilt=RxStr.indexOf(".");
int end=RxStr.indexOf(";");
if((spilt==-1)||(end==-1))continue;//if Not A Currect CMD, PASS this Node
qDebug()<<"GOT CMD";
if(myserial->isOpen())myserial->write(RxBuff);
int paraI=RxStr.indexOf("function:SimpleRate.");
if(paraI!=-1)
{
int Len=strlen("function:SimpleRate.");
QString Para=RxStr.mid(paraI+Len,end-paraI-Len);
int SRate=Para.toInt();
int SampleMap[]={500,1000,2500,5000,10000,25000,50000,100000,250000,500000,1000000};
for(int i=0;i<11;i++)
{
if(SampleMap[i]==SRate)
{
ui->comboBoxSampleRate->setCurrentIndex(i);
SRate=0;
break;
}
}
if(SRate)
ui->comboBoxSampleRate->setEditText(QString::number(SRate));
}
paraI=RxStr.indexOf("function:WindowLenth.");
if(paraI!=-1)
{
int Len=strlen("function:WindowLenth.");
QString Para=RxStr.mid(paraI+Len,end-paraI-Len);
int SRate=Para.toInt();
int WindowMap[]={128,256,512,1024,2048,4096};
for(int i=0;i<6;i++)
{
if(WindowMap[i]==SRate)
{
ui->comboBoxBuffer->setCurrentIndex(i);
SRate=0;
break;
}
}
if(SRate)
ui->comboBoxBuffer->setEditText(QString::number(SRate));
}
}
}
本段代码主要实现了根据数组内容匹配命令参数,并将示波器客户端的设置数据同步给服务端的下拉框。
Step 3:最终演示
本项目最终实现了PC、Android客户端,其联调效果如下:
旭日X3派上位机(左下角,服务器)
PC界面(背景,客户端)
Android界面(背景,客户端)
Part 5:代码与工具下载
界面源码:https://pan.baidu.com/s/11Imr6sGIyLAp00uvlTLqGQ?pwd=Yuki
TCP调试工具:https://pan.baidu.com/s/1eEffRw492zcfHN0iZkUcTw?pwd=Yuki
评论(0)
您还未登录,请登录后发表或查看评论