一、环境条件
上位机开发环境:Qt Creator 4.8.1
下位机开发环境:树莓派4b
此处省略Qt前端设计,只阐述后端逻辑实现
二、框架构建
上位机:
下位机:
三、程序编写(Qt)
1.程序初始界面
初始界面包含登录,网络控制,串口控制三个按钮。此时为未登录状态,登录标志按钮flag=0,此时点击其中的任意按钮,都会跳转到登录按钮。登录成功后,标志按钮flag=1,此时再点击登录按钮,只会弹出QMessageBox消息框提示登录成功,点击网络控制按钮则跳转到网络控制界面,点击串口控制按钮则会跳转到串口控制页面。
.h文件:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_logButton_clicked();
void on_networkButton_clicked();
void on_serialButton_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
.c文件:
#include "widget.h"
#include "ui_widget.h"
#include "login.h"
#include "QMessageBox"
#include <network.h>
#include <serial.h>
extern int flag;
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_logButton_clicked()
{
if(flag==0)//登录标志位flag=0
{
login *lg=new login;
lg->setGeometry(this->geometry());
lg->show();
this->close();
}
else if (flag==1) {
QMessageBox::information(this,"提示","您已成功登录");
}
}
void Widget::on_networkButton_clicked()
{
if(flag==0)
{
login *lg=new login;
lg->setGeometry(this->geometry());
lg->show();
this->close();
}
else if (flag==1) {
network *net=new network;
net->setGeometry(this->geometry());
net->show();
}
}
void Widget::on_serialButton_clicked()
{
if(flag==0)
{
login *lg=new login;
lg->setGeometry(this->geometry());
lg->show();
this->close();
}
else if (flag==1) {
serial *se=new serial;
se->setGeometry(this->geometry());
se->show();
this->close();
}
}
2.登录界面
登录框输入账号密码,点击"登录",拉取账号密码与数据库进行比对。若对比成功,登录标志flag置1,定时器启动,弹出QMessageBox消息框提示登录成功,保持1.5s,后自动关闭。若失败,登录标志不改变,弹出QMessageBox消息框提示登录失败。
.h文件
#ifndef LOGIN_H
#define LOGIN_H
#include <QWidget>
namespace Ui {
class login;
}
class login : public QWidget
{
Q_OBJECT
public:
explicit login(QWidget *parent = nullptr);
~login();
private slots:
void on_pushButton_clicked();
void on_backButton_clicked();
private:
Ui::login *ui;
};
#endif // LOGIN_H
.c文件
#include "login.h"
#include "ui_login.h"
#include "QString"
#include "widget.h"
#include "QMessageBox"
#include "QTimer"
login::login(QWidget *parent) :
QWidget(parent),
ui(new Ui::login)
{
ui->setupUi(this);
setWindowIcon(QIcon(":/home.ico"));
}
login::~login()
{
delete ui;
}
extern int flag=0;
void login::on_pushButton_clicked()
{
QString username=ui->countEdit->text();
QString passwd=ui->passwdEdit->text();
if(username=="8888"&&passwd=="6688")
{
Widget *wd=new Widget;
wd->setGeometry(this->geometry());
wd->show();
flag=1;
QMessageBox *box=new QMessageBox(QMessageBox::Information,"提示","登录成功");
QTimer::singleShot(1500,box,SLOT(close()));
this->close();
}
else {
QMessageBox::critical(this,"提示","密码或用户名错误");
}
}
void login::on_backButton_clicked()
{
Widget *wd=new Widget;
wd->setGeometry(this->geometry());
wd->show();
this->close();
}
3.网络控制界面
TCP连接与发送:界面初始化时,就已经初始化好tcpSocket,输入目的ip地址,目的端口后,点击连接,TCP开始与树莓派服务器连接,若连接成功,会接收到连接成功信号,触发绑定的槽函数并弹出QMessageBox消息框提示登录成功,并建立新的TCP连接。随后调节智能家电的参数,点击发送按钮,会将参数打包成TCP数据包,发送到树莓派控制硬件。
UDP连接与发送:网络控制界面开启时,就已经初始化好udpSocket,输入本地端口,点击接收开启连接,若连接成功弹出QMessageBox消息框,若此时有数据传输,则触发接收槽函数,并将接收到的内容显示到lable框中。
.h文件
#ifndef NETWORK_H
#define NETWORK_H
#include <QWidget>
#include <QTcpServer>
#include <QTcpSocket>
#include <QUdpSocket>
namespace Ui {
class network;
}
class network : public QWidget
{
Q_OBJECT
public:
explicit network(QWidget *parent = nullptr);
~network();
QTcpSocket *tcpSocket;
QUdpSocket *udpSocket;
private slots:
void on_horizontalSlider_valueChanged(int value);
void on_spinBox_valueChanged(int arg1);
void on_openTVButton_clicked();
void on_upButton_clicked();
void on_nextButton_clicked();
void on_TCPconnectButton_clicked();
void connect_slot();
void readyRead_slot();
void readyReadUDP_slot();
void on_TCPcloseButton_clicked();
void on_sendButton_clicked();
void on_UDPconnectButton_clicked();
void on_UDPcloseButton_clicked();
private:
Ui::network *ui;
};
#endif // NETWORK_H
.c文件
#include "network.h"
#include "ui_network.h"
#include <widget.h>
#include <iostream>
#include "QMessageBox"
#include "QString"
using namespace std;
static int lightnum;//灯的数值
static int airnum=15;//空调的数值
static int TVon=0;//0表示电视关闭
static int TVchannle=35;//表示电视频道
network::network(QWidget *parent) :
QWidget(parent),
ui(new Ui::network)
{
ui->setupUi(this);
setWindowIcon(QIcon(":/home.ico"));
tcpSocket=new QTcpSocket(this);
udpSocket=new QUdpSocket(this);
}
network::~network()
{
delete ui;
Widget *wd=new Widget;
wd->setGeometry(this->geometry());
wd->show();
}
void network::on_horizontalSlider_valueChanged(int value)
{
lightnum=ui->horizontalSlider->value();
ui->light_value_label->setText(QString::number(lightnum));
}
void network::on_spinBox_valueChanged(int arg1)
{
airnum=ui->spinBox->value();
cout<<airnum<<endl;
}
void network::on_openTVButton_clicked()
{
if(TVon==0)
{
TVon=1;
ui->openTVButton->setText("关");
}
else if (TVon==1) {
TVon=0;
ui->openTVButton->setText("开");
}
}
void network::on_upButton_clicked()
{
if(TVon==1)
{
TVchannle=TVchannle+1;
if(TVchannle=100)
{
TVchannle=10;
}
cout<<TVchannle<<endl;
}
}
void network::on_nextButton_clicked()
{
if(TVon==1)
{
TVchannle=TVchannle-1;
if(TVchannle==10)
{
TVchannle=99;
}
cout<<TVchannle<<endl;
}
}
void network::on_TCPconnectButton_clicked()
{
//tcpSocket->connectToHost(ui->ipaddrEdit->text(),ui->aimportEdit->text().toUInt());
tcpSocket->connectToHost("192.168.43.146",50001);
connect(tcpSocket,SIGNAL(connected()),this,SLOT(connect_slot()));
}
void network::connect_slot()
{
QMessageBox(QMessageBox::Information,"提示","连接成功");
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(readyRead_slot()));
}
void network::readyRead_slot()
{
cout<<"TCP数据来喽!!"<<endl;
}
void network::on_TCPcloseButton_clicked()
{
tcpSocket->close();
}
void network::on_sendButton_clicked()
{
QString temp;
if(lightnum>9)
{
temp.sprintf("%d%d%d%d",lightnum,airnum,TVon,TVchannle);
}
else if (0<=lightnum<=9) {
temp.sprintf("0%d%d%d%d",lightnum,airnum,TVon,TVchannle);
}
tcpSocket->write(temp.toLocal8Bit().data());
}
void network::on_UDPconnectButton_clicked()
{
if(udpSocket->bind(5554))
{
QMessageBox::information(this,"提示","UDP打开成功");
}
else {
QMessageBox::critical(this,"提示","UDP打开失败");
}
connect(udpSocket,SIGNAL(readyRead()),SLOT(readyReadUDP_slot()));
}
void network::readyReadUDP_slot()
{
cout<<"UDP数据来咯!"<<endl;
while (udpSocket->hasPendingDatagrams()) {
QByteArray array;
array.resize(udpSocket->pendingDatagramSize());
udpSocket->readDatagram(array.data(),array.size());
QString buf;
buf=array.data();
ui->plainTextEdit->appendPlainText(buf);
}
}
void network::on_UDPcloseButton_clicked()
{
udpSocket->close();
}
4.串口控制界面
串口界面打开时,使用foreach函数将可用的串口查找出来,保存到QStringList的变量中并在界面上显示,将串口各个参数配置好后,点击"打开",配置的参数被赋给serialPort结构体的各个参数,并触发serialPort的open函数打开串口,若打开成功,QMessageBox弹窗提示打开成功。若打开失败,QMessageBox弹窗提示。
.h文件
#ifndef SERIAL_H
#define SERIAL_H
#include <QWidget>
#include <QSerialPortInfo>
#include <QSerialPort>
#include <QTimer>
#include <QTime>
namespace Ui {
class serial;
}
class serial : public QWidget
{
Q_OBJECT
public:
explicit serial(QWidget *parent = nullptr);
~serial();
QSerialPort *serialPort;
QTimer timer;
private slots:
void on_openButton_clicked();
void on_closeButton_clicked();
void serialPort_ReadyRead_slot();
void timeout_slot();
void on_snedButton_clicked();
void on_clearButton_clicked();
void on_backButton_clicked();
private:
Ui::serial *ui;
};
#endif // SERIAL_H
.c文件
#include "serial.h"
#include "ui_serial.h"
#include "QMessageBox"
#include "widget.h"
serial::serial(QWidget *parent) :
QWidget(parent),
ui(new Ui::serial)
{
ui->setupUi(this);
setWindowIcon(QIcon(":/home.ico"));
QStringList serialNamePort;
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
{
serialNamePort<<info.portName();
}
ui->serialcomboBox->addItems(serialNamePort);
connect(&timer,SIGNAL(timeout()),this,SLOT(timeout_slot()));
}
serial::~serial()
{
delete ui;
}
void serial::serialPort_ReadyRead_slot()
{
QString buf;
QTime time=QTime::currentTime();
buf=QString(serialPort->readAll());
ui->revcTextEdit->appendPlainText(time.toString("hh:mm:ss"));
ui->revcTextEdit->appendPlainText(buf);
}
void serial::timeout_slot()
{
}
void serial::on_openButton_clicked()
{
serialPort=new QSerialPort(this);
QSerialPort::BaudRate buadRate;
QSerialPort::DataBits dataBits;
QSerialPort::StopBits stopBits;
QSerialPort::Parity checkBits;
if(ui->botecomboBox->currentText()=="4800")
{
buadRate=QSerialPort::Baud4800;
}
else if (ui->botecomboBox->currentText()=="9600") {
buadRate=QSerialPort::Baud9600;
}
else if (ui->botecomboBox->currentText()=="19200") {
buadRate=QSerialPort::Baud19200;
}
else if (ui->botecomboBox->currentText()=="115200") {
buadRate=QSerialPort::Baud115200;
}
if(ui->datacomboBox->currentText()=="5")
{
dataBits=QSerialPort::Data5;
}
else if (ui->datacomboBox->currentText()=="6") {
dataBits=QSerialPort::Data6;
}
else if (ui->datacomboBox->currentText()=="7") {
dataBits=QSerialPort::Data7;
}
else if (ui->datacomboBox->currentText()=="8") {
dataBits=QSerialPort::Data8;
}
if(ui->stopcomboBox->currentText()=="1")
{
stopBits=QSerialPort::OneStop;
}
else if (ui->stopcomboBox->currentText()=="3") {
stopBits=QSerialPort::OneAndHalfStop;
}
else if (ui->stopcomboBox->currentText()=="2") {
stopBits=QSerialPort::TwoStop;
}
if(ui->jiaoyancomboBox->currentText()=="none")
{
checkBits=QSerialPort::NoParity;
}
serialPort->setPortName(ui->serialcomboBox->currentText());
serialPort->setBaudRate(buadRate);
serialPort->setDataBits(dataBits);
serialPort->setStopBits(stopBits);
serialPort->setParity(checkBits);
if(serialPort->open(QIODevice::ReadWrite)==true)
{
QMessageBox::information(this,"提示","成功");
connect(serialPort,SIGNAL(readyRead()),this,SLOT(serialPort_ReadyRead_slot()));
}
else {
QMessageBox::critical(this,"提示","失败");
}
}
void serial::on_closeButton_clicked()
{
serialPort->close();
}
void serial::on_snedButton_clicked()
{
serialPort->write(ui->snedlineEdit->text().toLocal8Bit().data());
}
void serial::on_clearButton_clicked()
{
ui->revcTextEdit->clear();
ui->snedlineEdit->clear();
}
void serial::on_backButton_clicked()
{
Widget *wd=new Widget;
wd->setGeometry(this->geometry());
wd->show();
this->close();
}
四、程序编写(树莓派wiringPi)
程序启动后,首先调用pthread_create创建2个线程tcp\serial,并使用pthread_detach分离线程。串口线程创建成功后,使用serialOpen打开串口,配置参数,发送验证性字符随后进入while(1)采集并发送环境信息。tcp线程创建后等待客户端连接,客户端连接成功后,创建第3个udp线程,随后进入while(1)等待tcp数据并处理。udp线程创建成功后,创建套接字,绑定端口和ip地址,等待连接,连接成功后进入while(1)进入发送
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <time.h>
#include <wiringPi.h>
#include <wiringSerial.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <stdlib.h>
#include <signal.h>
char msg[20];
void *UDP(void)
{
pthread_detach(pthread_self());//将当前线程分离
//创建套接字
int sock_fd = socket(AF_INET,SOCK_DGRAM,0);
//绑定地址(IP+PORT)
struct sockaddr_in srvaddr,ciladdr;
srvaddr.sin_family = AF_INET;//地址族
//端口
srvaddr.sin_port = htons(5554); //端口一般取50000以上
//IP地址
srvaddr.sin_addr.s_addr = inet_addr("192.168.43.210");//将文本字符串转换为32位无符号网络地址
char buf[100];
int len = sizeof(ciladdr);
while(1)
{
bzero(buf,100);
fgets(buf,100,stdin); //从键盘输入数据
sendto(sock_fd,buf,100,0,(struct sockaddr *)&srvaddr,sizeof(srvaddr));
}
//关闭套接字
close(sock_fd);
}
int light;
int airnum;
int TVon;
int TVchannel;
void *TCPServer(void)
{
pthread_detach(pthread_self());//将当前线程分离
//创建套接字
int sock_fd = socket(PF_INET,SOCK_STREAM,0);
//绑定地址(IP+PORT)
struct sockaddr_in srvaddr;
srvaddr.sin_family = PF_INET;//地址族
//端口
srvaddr.sin_port = htons(50001); //端口一般取50000以上
//IP地址
srvaddr.sin_addr.s_addr = inet_addr("192.168.43.146");//将文本字符串转换为32位无符号网络地址
bind(sock_fd,(struct sockaddr *)&srvaddr,sizeof(srvaddr));
//设置监听套接字
listen(sock_fd,4);
//等待客户端连接
printf("等待连接\n");
int conn_fd = accept(sock_fd,NULL,NULL);
printf("连接成功\n");
pthread_t th3;
pthread_create(&th3,NULL,UDP,NULL);
while(1)
{
bzero(msg,20);
recv(conn_fd,msg,20,0);
printf("recv:%s\n",msg);
light=(msg[0]-48)*10+(msg[1]-48);
airnum=(msg[2]-48)*10+(msg[3]-48);
TVon=msg[4]-48;
TVchannel=(msg[5]-48)*10+(msg[6]-48);
}
//关闭套接字
close(conn_fd);
close(sock_fd);
}
void *serial()
{
pthread_detach(pthread_self());//将当前线程分离
int fd;
int cmd;
int i=0;
if(wiringPiSetup()==-1)
{
printf("setup wiringPi failed!\n");
return 1;
}
fd=serialOpen("/dev/ttyAMA0",115200);
if(-1==fd)
{
printf("ttyAMA open error!\n");
}
serialPuts(fd,"message from uart\r\n");
printf("uart send finish\n");
delay(1000);
while(1)
{
serialPuts(fd,"Temp: Lum:\0");
delay(3000);
serialPrintf(fd,"%d %.1f\r\n",(airnum+5),light*0.657);
delay(3000);
// serialFlush(fd);
}
serialClose(fd);
}
void signal_handler()
{
static int i=0;
i++;
if(i<=light)
{
digitalWrite(7,HIGH);
}
else if(light<i<100)
{
digitalWrite(7,LOW);
}
if(100==i)
{
i=0;
}
}
int main()
{
pthread_t th1,th2;
pthread_create(&th1,NULL,TCPServer,NULL);
pthread_create(&th2,NULL,serial,NULL);
if(wiringPiSetup()==-1)
{
printf("setup wiringPi faild!");
return 1;
}
struct itimerval itv;
itv.it_interval.tv_sec=0;
itv.it_interval.tv_usec=500;
itv.it_value.tv_sec=1;
itv.it_value.tv_usec=0;
pinMode(7,OUTPUT);
pinMode(1,OUTPUT);
pinMode(0,OUTPUT);
setitimer(ITIMER_REAL,&itv,NULL);
signal(SIGALRM,signal_handler);
while(1)
{
if(airnum==15)
{
digitalWrite(1,LOW);
}
else if(airnum>16)
{
digitalWrite(1,HIGH);
}
if(TVon==0)
{
digitalWrite(0,LOW);
}
else if(TVon==1)
{
digitalWrite(0,HIGH);
}
};
return 0;
}
五、结语
这个项目做的马马虎虎,很多设想的功能都是通过模拟的方式实现,还有许多不足之处需要好好打磨斟酌。因此有错误之处,还请批评指正。