QT应用篇
一、QT上位机串口编程
二、QML用Image组件实现Progress Bar 的效果
三、QML自定义显示SpinBox的加减按键图片及显示值效果
四、window编译LibModbus库并用QT编写一个Modbus主机
五、手把手教学用QT编写TCP上位机并显示温湿度
文章目录
- QT应用篇
- 软件篇
- 1.新建QT工程
- 2.UI设计
- 1.添加标签
- 2.添加按钮 输入框和显示温湿度标签
- 3.功能设计
- 结果展示
目标:
1.上位机显示温湿度
2.qt编写tcp服务器端并成功通讯
软件:
TCP客户端
我这里我用的时候通讯猫:下载地址:https://wws.lanzoui.com/iE96Rpk2cxc
软件篇
1.新建QT工程
1.首先,新建工程
2.然后自定义项目名称和路径(路径和名称不能有中文)
3.构建系统
一般是默认直接下一步
然后自己定义类名称
2.这里最好勾一下,这样就可以快速的先完成界面设计
如果不✓上的话,就要自己手动写代码来完成UI设计
这里直接下一步
编译套件一般来说window大部分选择MinGw64
下一步
如你所看到的,这里就算完成QT工程创建了
试着先编译一下快捷键 CTRL+R
或者点击左下角绿色三角形
编译成功
可以看到弹出一个空白界面
双击.ui文件,打开界面设计
图片中的红色框框里面代表许多控件
我就不一一介绍了
找到label控件,拖到界面中
随便输入点内容
然后点击编译
到这里创建工程部分就算结束了
2.UI设计
1.添加标签
这四个都是标签,所以不用修改名称
2.添加按钮 输入框和显示温湿度标签
添加完成后在右下角的控件属性那里可以看到控件的属性
位置,大小,字体等等
顺便修改字体大小和控件名称,方便后续自己用的时候好找一点
左上角的标签的名称我也修改了,后续用来显示日期和时间
按钮的属性的check 选中,把按钮的信号槽函数添加到文件里面,这里用的是官方的自动生成功能
可以看到cpp文件和h文件已经生成槽函数了
后续可以在里面添加功能来实现按钮的点击作用
到这里UI部分暂时设计完成
3.功能设计
双击打开TCPtest.pro文件 在第一行 后面加上 network
然后开是编写TCP服务器部分
由于是TCP通讯,所以要添加TCP部分的控件
打开tcptest.h
文件添加内容
#include <QTcpServer> //服务器头文件
#include <QTcpSocket> //客户端头文件
#include <QList> //链表头文件用来存放客户端容器
#include <QDebug> //打印
#include <QTimer> //定时器
#include <QDateTime> //日期时间
#include <QTime> //时间
#include <QDate> //日期
#include <QMessageBox> //弹窗#define LOG qDebug()<<QTime::currentTime() //
在public:
添加
//定义服务器指针QTcpServer *tcp_server;//定义客户端指针链表容器QList<QTcpSocket *> clientList;QTcpSocket *socket;
在private slots:
添加函数
void timer_500ms();void newConnectionSlot();void readyReadSlot(); //自定义处理readyRead信号的槽函数void startServer();void stopServer();void dateProcess(QByteArray qstr);
在private:
添加一个定时器
QTimer *t_timer500;
添加完成后如图
打开tcptest.cpp
在ui->setupUi(this);
后面添加
//给服务器指针实例化对象tcp_server = new QTcpServer(this); //服务器创建完成t_timer500 = new QTimer(this); //实例化定时器t_timer500->start(500); //开始运行,500ms回调一次connect(t_timer500,SIGNAL(timeout()),this,SLOT(timer_500ms())); //手动连接定时器的信号和槽函数
然后在构建槽函数内需要实现的的功能
1.timer_500ms()
这里要是实现左上角的日期标签刷新显示时间
void TCPtest::timer_500ms()//500ms运行一次的函数
{QDateTime datetime;datetime = QDateTime::currentDateTime(); //把日期时间赋值刷新ui->label->setText(datetime.toString("yyyy/MM/dd hh:mm:ss")); //label为左上角显示日期的标签
}
2.startServer()
这里要开始创建TCP服务器,获取监听的端口,并建立监听
void TCPtest::startServer()
{//获取UI界面的端口号quint16 port = ui->led_port->text().toUInt();ui->led_port->setEnabled(false);//返回值:成功返回真,失败返回假if(!tcp_server->listen(QHostAddress::Any,port)){LOG<<"Error!:"<<tcp_server->errorString();QMessageBox::critical(this, "失败", "服务器启动失败");}//执行到这表明服务器启动成功,并对客户端连接进行监听,如果有客户端向服务器发来连接请求,那么该服务器就会自动发射一个newConnection信号//我们可以将信号连接到对应的槽函数中处理相关逻辑connect(tcp_server, &QTcpServer::newConnection, this, &TCPtest::newConnectionSlot);
}
3.newConnectionSlot()
这里要把客户端套接字存放起来,再手动连接readyRead信号和槽函数
void TCPtest::newConnectionSlot()
{//获取连接客户端套接字socket = tcp_server->nextPendingConnection();//套接字放到客户端容器clientList.push_back(socket);//客户端有数据向服务器发送过来,套接字自动发送一个readyread信号//将信号连接到自定义的槽函数中处理connect(socket, &QTcpSocket::readyRead, this, &TCPtest::readyReadSlot);QHostAddress clientAddress = socket->peerAddress();//获取客户端IPQString clientIP = clientAddress.toString();LOG << "A Client IP:" << clientIP<<"requests a connection";
}
4.readyReadSlot()
删除无效客户端,然后从所有连接的客户端查找是哪一个客户端发的数据
void TCPtest::readyReadSlot()
{//删除客户端链表套接字for(int i=0; i<clientList.count(); i++){if(clientList[i]->state() == 0){clientList.removeAt(i); //将下标为i的客户端移除LOG<<"removeAt clientList "<<i;}}//遍历所有客户端,查看是哪个客户端发来数据for(int i=0; i<clientList.count(); i++){if(clientList[i]->bytesAvailable() != 0){QByteArray msg = clientList[i]->readAll();dateProcess(msg);}}
}//自定义处理readyRead信号的槽函数
5.dateProcess(QByteArray qstr)
处理客户端收到的数据
void TCPtest::dateProcess(QByteArray qstr)//处理数据
{LOG<<"msg"<<qstr;}
这里我们还没有定义数据,就先打印接收到的数据
6.stopServer()
关闭服务器,删除无效套接字
void TCPtest::stopServer()
{ui->led_port->setEnabled(true); //使能端口输入if(tcp_server->isListening()){socket->disconnectFromHost();//删除客户端链表中的无效客户端套接字for(int i=0; i<clientList.count(); i++){if(clientList[i]->state() == 0){clientList.removeAt(i); //将下标为i的客户端移除LOG<<"remove client :"<<i;}}tcp_server->disconnect();tcp_server->close();}else{LOG<<"close server";tcp_server->disconnect();tcp_server->close();}
}
7.on_btn_open_toggled(bool checked)
最后处理按键槽函数,把创建服务器和关闭服务器放到里面
void TCPtest::on_btn_open_toggled(bool checked)
{if(checked == 1){startServer();ui->btn_open->setText("打开");}else{stopServer();ui->btn_open->setText("关闭");}}
最后在TCPtest::~TCPtest()
里面也加上关闭服务器的函数,避免意外关闭窗口的时候服务器没关闭
TCPtest::~TCPtest()
{delete ui;stopServer();
}
最后如下图
然后点击编译
成功后如图
点击按钮打开服务器
然后打开下载好的TCP客户端
然后点击启用
结果展示
如图
到这里说明TCP通讯已经成功了
那接下来该给发送的数据定义一个格式了
一般来说最简单的通讯协议主要有帧头,命令类型,数据,数据长度,校验,帧尾等等
在这里可以稍微简化一些
只要帧头和命令类型和数据就行了
例如:EE A0 00 64 帧头定义:0xEE 命令类型:0xA0开始 数据:100
则在客户端里面输入EE A0 00 64
那现在回过头来编写dateProcess(QByteArray qstr)
void TCPtest::dateProcess(QByteArray qstr)//处理数据
{unsigned char cmd;uint16_t data;QByteArray read_buf;if (qstr.isEmpty()){return ;}read_buf += qstr;LOG<<"接收长度"<<read_buf.length()<<"msg"<<read_buf;while (read_buf.length() >= 4){if (read_buf.at(0) == (char)0xEE){cmd = read_buf.at(1);if (cmd >= 0xA0){QByteArray frame = read_buf.left(4); //取前4个buffmemcpy(&data, frame.right(2).data(), 2); //取出数据read_buf.remove(0, 8);LOG<<"接收内容"<<frame;LOG<<"data"<<data;}else{read_buf.remove(0, 1);}}else{read_buf.remove(0, 4);//帧头不对,除掉一包数据}}}
编译后用客户端发两条命令
EEA01000
EEA11600
如图
这里已经把数据分开解析了,接下来在创建一个函数,用来区分命令是A0的时候干啥,A1的时候干啥
在cpp文件中
void TCPtest::analysis(unsigned char cmd,int data)
{switch (cmd){case 0xA0:{LOG<<"温度 "<<data;ui->lbl_temp->setNum(data);}case 0xA1:{LOG<<"湿度 "<<data;ui->lbl_hum->setNum(data);}}}
并在h文件中添加
void analysis(unsigned char cmd,int data);
同时在dateProcess
中调用这个函数
编译后运行在通过TCP客户端发送数据包
注意:必须选择16进制发送
当然,也可以试一下多包数据一起发
代码如下
tcptest.cpp
#include "tcptest.h"
#include "ui_tcptest.h"TCPtest::TCPtest(QWidget *parent): QMainWindow(parent), ui(new Ui::TCPtest)
{ui->setupUi(this);//给服务器指针实例化对象tcp_server = new QTcpServer(this); //服务器创建完成t_timer500 = new QTimer(this); //实例化定时器t_timer500->start(500); //开始运行,500ms回调一次connect(t_timer500,SIGNAL(timeout()),this,SLOT(timer_500ms())); //手动连接定时器的信号和槽函数
}TCPtest::~TCPtest()
{delete ui;stopServer();
}void TCPtest::timer_500ms()//500ms运行一次的函数
{QDateTime datetime;datetime = QDateTime::currentDateTime(); //把日期时间赋值刷新ui->label->setText(datetime.toString("yyyy/MM/dd hh:mm:ss")); //label为左上角显示日期的标签
}void TCPtest::startServer()
{//获取UI界面的端口号quint16 port = ui->led_port->text().toUInt();ui->led_port->setEnabled(false);//返回值:成功返回真,失败返回假if(!tcp_server->listen(QHostAddress::Any,port)){LOG<<"Error!:"<<tcp_server->errorString();QMessageBox::critical(this, "失败", "服务器启动失败");}//执行到这表明服务器启动成功,并对客户端连接进行监听,如果有客户端向服务器发来连接请求,那么该服务器就会自动发射一个newConnection信号//我们可以将信号连接到对应的槽函数中处理相关逻辑connect(tcp_server, &QTcpServer::newConnection, this, &TCPtest::newConnectionSlot);
}void TCPtest::newConnectionSlot()
{//获取连接客户端套接字socket = tcp_server->nextPendingConnection();//套接字放到客户端容器clientList.push_back(socket);//客户端有数据向服务器发送过来,套接字自动发送一个readyread信号//将信号连接到自定义的槽函数中处理connect(socket, &QTcpSocket::readyRead, this, &TCPtest::readyReadSlot);QHostAddress clientAddress = socket->peerAddress();//获取客户端IPQString clientIP = clientAddress.toString();LOG << "A Client IP:" << clientIP<<"requests a connection";
}void TCPtest::readyReadSlot()
{//删除客户端链表套接字for(int i=0; i<clientList.count(); i++){if(clientList[i]->state() == 0){clientList.removeAt(i); //将下标为i的客户端移除LOG<<"removeAt clientList "<<i;}}//遍历所有客户端,查看是哪个客户端发来数据for(int i=0; i<clientList.count(); i++){if(clientList[i]->bytesAvailable() != 0){QByteArray msg = clientList[i]->readAll();dateProcess(msg);}}
}//自定义处理readyRead信号的槽函数void TCPtest::dateProcess(QByteArray qstr)//处理数据
{unsigned char cmd;uint16_t data;QByteArray read_buf;if (qstr.isEmpty()){return ;}read_buf += qstr;LOG<<"接收长度"<<read_buf.length()<<"msg"<<read_buf;while (read_buf.length() >= 4){if (read_buf.at(0) == (char)0xEE){cmd = read_buf.at(1);if (cmd >= 0xA0){QByteArray frame = read_buf.left(4); //取前4个buffmemcpy(&data, frame.right(2).data(), 2); //取出数据read_buf.remove(0, 8);analysis(cmd,data);LOG<<"接收内容"<<frame;LOG<<"data"<<data;}else{read_buf.remove(0, 1);}}else{read_buf.remove(0, 4);//帧头不对,除掉一包数据}}
}//串口解析函数
void TCPtest::analysis(unsigned char cmd,int data)
{switch (cmd){case 0xA0:{LOG<<"温度 "<<data;ui->lbl_temp->setNum(data);break;}case 0xA1:{LOG<<"湿度 "<<data;ui->lbl_hum->setNum(data);break;}}}void TCPtest::stopServer()
{ui->led_port->setEnabled(true);if(tcp_server->isListening()){socket->disconnectFromHost();//删除客户端链表中的无效客户端套接字for(int i=0; i<clientList.count(); i++){if(clientList[i]->state() == 0){clientList.removeAt(i); //将下标为i的客户端移除LOG<<"remove client :"<<i;}}socket->close();tcp_server->disconnect();tcp_server->close();}else{LOG<<"close server";socket->close();tcp_server->disconnect();tcp_server->close();}
}void TCPtest::on_btn_open_toggled(bool checked)
{if(checked == 1){ui->btn_open->setText("关闭");startServer();}else{stopServer();ui->btn_open->setText("打开");}}
tcptest.h
#ifndef TCPTEST_H
#define TCPTEST_H#include <QMainWindow>
#include <QTcpServer> //服务器头文件
#include <QTcpSocket> //客户端头文件
#include <QList> //链表头文件用来存放客户端容器
#include <QDebug> //打印
#include <QTimer> //定时器
#include <QDateTime> //日期时间
#include <QTime> //时间
#include <QDate> //日期
#include <QMessageBox> //弹窗#define LOG qDebug()<<QTime::currentTime() //用于打印,带时间参数QT_BEGIN_NAMESPACE
namespace Ui { class TCPtest; }
QT_END_NAMESPACEclass TCPtest : public QMainWindow
{Q_OBJECTpublic:TCPtest(QWidget *parent = nullptr);~TCPtest();//定义服务器指针QTcpServer *tcp_server;//定义客户端指针链表容器QList<QTcpSocket *> clientList;QTcpSocket *socket;private slots:void on_btn_open_toggled(bool checked);void timer_500ms();void newConnectionSlot();void readyReadSlot(); //自定义处理readyRead信号的槽函数void startServer();void stopServer();void dateProcess(QByteArray qstr);void analysis(unsigned char cmd,int data);private:Ui::TCPtest *ui;QTimer *t_timer500;};
#endif // TCPTEST_H
当然后续如何想要真正的温湿度数据的话需要用过MCU采集传感器信息,在通过一个wifi模块连接上位机把数据发到上位机上即可
如果觉得对你有用,请点赞评论谢谢