首页 > 其他分享 >毕设拯救计划(二)基于QT的智能家居(Onenet云)

毕设拯救计划(二)基于QT的智能家居(Onenet云)

时间:2024-11-06 20:44:26浏览次数:3  
标签:毕设 txlen QT void 智能家居 mqtt MQTT QString txbuf

文章目录


前言

  前段时间,笔者觉得以前的STM32的智能家居太low了,于是想对其进行改进,目前的方案有以下两种:一、STM32和Linux开发板构成完整的智能车家系统,即通过MQTT云端进行双向传输,完成同时对应控件的相互下达。这部分的问题在于云端的双机通信其实很不稳定,即使加了心跳包保持连接仍然会时不时断链连(PS:可能有解决办法,笔者感觉意义不是很大就没有继续优化了);二、Linux端作为STM32的流媒体端,负责上云和娱乐系统包括视频,音乐(这部分已经完成了,视频部分还是同理有点懒),二者通过串口并写个传输协议控制,这个比较好实现,STM32端甚至可以不需要联网功能,但是有点网关的意思。


一、效果展示

  这个系列就不拐弯抹角了,先看效果为主,这里笔者主要在Windows下编辑并实验,Linux开发板上也运行成功了,但是上面没有集成音频输出的接口,两边的效果是一样的(泰山派的资源太少,笔者的扩展底板实在懒得焊)。
在这里插入图片描述

二、设计思路

  其实看得出来,这就是一个mqtt客户端改出来的小项目,加上一个音乐播放器和虚拟键盘的效果,目前阶段是完成了Onenet云的连接及任务下达和发送,双机的这部分还是有点问题(代码部分笔者最近整理好会开源的)。

2.1 Mqtt的实现

  这部分是我们的核心,它是基于TCP的框架下改进来的,感兴趣的话可以回顾笔者网络编程那一部分。这里主要是用了B站小龙哥开源的MQTT框架,这里再次感谢:

// mqtt.h
#ifndef MQTT_H
#define MQTT_H
extern "C"
{
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
}
#include <iostream>
#include <QWidget>
#include <QTcpServer>
#include <QHostInfo>  //获取计算机网络信息
#include <QUdpSocket>
#include <QtNetwork>
#include <QHostInfo>
#include <QDebug>
#include <QTcpSocket>
#include <QHostAddress>
#include <QDebug>
#include <QMessageBox>
#include <QLineEdit>
#include <QHBoxLayout>
#include <QComboBox>
#include <QFile>
#include <QTimer>
#include <QScrollBar>

#define BYTE0(dwTemp)       (*(char *)(&dwTemp))
#define BYTE1(dwTemp)       (*((char *)(&dwTemp) + 1))
#define BYTE2(dwTemp)       (*((char *)(&dwTemp) + 2))
#define BYTE3(dwTemp)       (*((char *)(&dwTemp) + 3))

typedef enum
{
//名字 	    值 			报文流动方向 	描述
M_RESERVED1	=0	,	//	禁止	保留
M_CONNECT		,	//	客户端到服务端	客户端请求连接服务端
M_CONNACK		,	//	服务端到客户端	连接报文确认
M_PUBLISH		,	//	两个方向都允许	发布消息
M_PUBACK		,	//	两个方向都允许	QoS 1消息发布收到确认
M_PUBREC		,	//	两个方向都允许	发布收到(保证交付第一步)
M_PUBREL		,	//	两个方向都允许	发布释放(保证交付第二步)
M_PUBCOMP		,	//	两个方向都允许	QoS 2消息发布完成(保证交互第三步)
M_SUBSCRIBE		,	//	客户端到服务端	客户端订阅请求
M_SUBACK		,	//	服务端到客户端	订阅请求报文确认
M_UNSUBSCRIBE	,	//	客户端到服务端	客户端取消订阅请求
M_UNSUBACK		,	//	服务端到客户端	取消订阅报文确认
M_PINGREQ		,	//	客户端到服务端	心跳请求
M_PINGRESP		,	//	服务端到客户端	心跳响应
M_DISCONNECT	,	//	客户端到服务端	客户端断开连接
M_RESERVED2		,	//	禁止	保留
}_typdef_mqtt_message;


class MQTT_WorkClass:public QObject
{
Q_OBJECT
public:

QTimer *timer=nullptr;
MQTT_WorkClass(QObject* parent=nullptr):QObject(parent){}
~MQTT_WorkClass();

//用户名初始化
void OneNet_LoginInit(char *ProductKey,char *DeviceName,char *DeviceSecret);
//MQTT协议相关函数声明
quint8 MQTT_PublishData(char *topic, char *message, quint8 qos);
quint8 MQTT_SubscribeTopic(char *topic,quint8 qos,quint8 whether);
void MQTT_Init(void);
quint8 MQTT_Connect(char *ClientID,char *Username,char *Password);
void MQTT_SentHeart(void);
void MQTT_Disconnect(void);
void MQTT_SendBuf(quint8 *buf,quint16 len);
void ConnectMqttServer(QString ip,quint16 port);
void Set_MQTT_Addr(QString ip,quint16 port,QString MQTT_ClientID,QString MQTT_UserName,QString MQTT_PassWord);
void StartEvenLoop();
public slots:
void EndEvenLoop();
void run();
void LocalTcpClientConnectedSlot();
void LocalTcpClientDisconnectedSlot();
void LocalTcpClientReadDtatSlot();
void LocalTcpClientBytesWrittenSlot(qint64 byte);
//订阅主题
void slot_SubscribeTopic(QString topic);
//发布消息
void slot_PublishData(QString topic,QString message);
//断开连接
void slot_tcp_close();
signals:
void LogSend(QString text);
void MQTT_ConnectState(bool state);
void ReceiveWechatData(const QByteArray& data);
private:
quint8 *mqtt_rxbuf;
quint8 *mqtt_txbuf;
quint16 mqtt_rxlen;
quint16 mqtt_txlen;
quint8 _mqtt_txbuf[256];//发送数据缓存区
quint8 _mqtt_rxbuf[256];//接收数据缓存区

QTcpSocket *LocalTcpClientSocket=nullptr;
QString m_ip;
quint16 m_port;

bool socket_type=0;  //这是网络的状态: 1表示已经连接 0表示未连接

QString m_MQTT_ClientID;
QString m_MQTT_UserName;
QString m_MQTT_PassWord;

QEventLoop loop;

QByteArray  ReadData;
};
#endif

// mqtt.c
#include "mqtt.h"
//连接成功服务器回应 20 02 00 00
//客户端主动断开连接 e0 00
const quint8 parket_connetAck[] = {0x20,0x02,0x00,0x00};
const quint8 parket_disconnet[] = {0xe0,0x00};
const quint8 parket_heart[] = {0xc0,0x00};
const quint8 parket_heart_reply[] = {0xc0,0x00};
const quint8 parket_subAck[] = {0x90,0x03};

MQTT_WorkClass::~MQTT_WorkClass()
{
    qDebug()<<"析构函数---TCP";
}

void MQTT_WorkClass::run()
{
    qDebug()<<"执行:run";

    if(timer)
    {
        delete  timer;
        timer=nullptr;
    }
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(EndEvenLoop()));

    socket_type=0;
    //连接到服务器
    ConnectMqttServer(m_ip,m_port);
    //开始事件循环
    StartEvenLoop();

    //初始化mqtt协议
    MQTT_Init();

    //连接mqtt协议
    if(MQTT_Connect(m_MQTT_ClientID.toUtf8().data(),m_MQTT_UserName.toUtf8().data(),m_MQTT_PassWord.toUtf8().data()))
    {
        LogSend("MQTT服务器登录失败.\n");
    }
    else
    {
        LogSend("MQTT服务器登录成功.\n");
    }
}


void MQTT_WorkClass::MQTT_Init(void)
{
    //缓冲区赋值
    mqtt_rxbuf = _mqtt_rxbuf;
    mqtt_rxlen = sizeof(_mqtt_rxbuf);
    mqtt_txbuf = _mqtt_txbuf;
    mqtt_txlen = sizeof(_mqtt_txbuf);
    memset(mqtt_rxbuf,0,mqtt_rxlen);
    memset(mqtt_txbuf,0,mqtt_txlen);
}

/*
函数功能: 登录服务器
函数返回值: 0表示成功 1表示失败
*/
quint8 MQTT_WorkClass::MQTT_Connect(char *ClientID,char *Username,char *Password)
{
    quint8 i,j;
    int ClientIDLen = strlen(ClientID);
    int UsernameLen = strlen(Username);
    int PasswordLen = strlen(Password);
    int DataLen;
    mqtt_txlen=0;
    //可变报头+Payload  每个字段包含两个字节的长度标识
    DataLen = 10 + (ClientIDLen+2) + (UsernameLen+2) + (PasswordLen+2);

    //固定报头
    //控制报文类型
    mqtt_txbuf[mqtt_txlen++] = 0x10;		//MQTT Message Type CONNECT
    //剩余长度(不包括固定头部)
    do
    {
        quint8 encodedByte = DataLen % 128;
        DataLen = DataLen / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( DataLen > 0 )
            encodedByte = encodedByte | 128;
        mqtt_txbuf[mqtt_txlen++] = encodedByte;
    }while ( DataLen > 0 );

    //可变报头
    //协议名
    mqtt_txbuf[mqtt_txlen++] = 0;        	// Protocol Name Length MSB
    mqtt_txbuf[mqtt_txlen++] = 4;           // Protocol Name Length LSB
    mqtt_txbuf[mqtt_txlen++] = 'M';        	// ASCII Code for M
    mqtt_txbuf[mqtt_txlen++] = 'Q';        	// ASCII Code for Q
    mqtt_txbuf[mqtt_txlen++] = 'T';        	// ASCII Code for T
    mqtt_txbuf[mqtt_txlen++] = 'T';        	// ASCII Code for T
    //协议级别
    mqtt_txbuf[mqtt_txlen++] = 4;        		// MQTT Protocol version = 4   对于 3.1.1 版协议,协议级别字段的值是 4(0x04)
    //连接标志
    mqtt_txbuf[mqtt_txlen++] = 0xc2;        	// conn flags
    mqtt_txbuf[mqtt_txlen++] = 0;        		// Keep-alive Time Length MSB
    mqtt_txbuf[mqtt_txlen++] = 100;        	// Keep-alive Time Length LSB  100S心跳包    保活时间

    mqtt_txbuf[mqtt_txlen++] = BYTE1(ClientIDLen);// Client ID length MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(ClientIDLen);// Client ID length LSB
    memcpy(&mqtt_txbuf[mqtt_txlen],ClientID,ClientIDLen);
    mqtt_txlen += ClientIDLen;

    if(UsernameLen > 0)
    {
        mqtt_txbuf[mqtt_txlen++] = BYTE1(UsernameLen);		//username length MSB
        mqtt_txbuf[mqtt_txlen++] = BYTE0(UsernameLen);    	//username length LSB
        memcpy(&mqtt_txbuf[mqtt_txlen],Username,UsernameLen);
        mqtt_txlen += UsernameLen;
    }

    if(PasswordLen > 0)
    {
        mqtt_txbuf[mqtt_txlen++] = BYTE1(PasswordLen);		//password length MSB
        mqtt_txbuf[mqtt_txlen++] = BYTE0(PasswordLen);    	//password length LSB
        memcpy(&mqtt_txbuf[mqtt_txlen],Password,PasswordLen);
        mqtt_txlen += PasswordLen;
    }

    //清空数据
    memset(mqtt_rxbuf,0,mqtt_rxlen);
    ReadData.clear();

    MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);
    //开始事件循环
    StartEvenLoop();

    if(ReadData.length()==0)
    {
        //开始事件循环
        StartEvenLoop();
    }
    memcpy((char *)mqtt_rxbuf,ReadData.data(),ReadData.length());

    //CONNECT
    if(mqtt_rxbuf[0]==parket_connetAck[0] && mqtt_rxbuf[1]==parket_connetAck[1]) //连接成功
    {
        return 0;//连接成功
    }
    return 1;
}


/*
函数功能: MQTT订阅/取消订阅数据打包函数
函数参数:
    topic       主题
    qos         消息等级 0:最多分发一次  1: 至少分发一次  2: 仅分发一次
    whether     订阅/取消订阅请求包 (1表示订阅,0表示取消订阅)
返回值: 0表示成功 1表示失败
*/
quint8 MQTT_WorkClass::MQTT_SubscribeTopic(char *topic,quint8 qos,quint8 whether)
{
    quint8 i,j;
    mqtt_txlen=0;
    int topiclen = strlen(topic);

    int DataLen = 2 + (topiclen+2) + (whether?1:0);//可变报头的长度(2字节)加上有效载荷的长度
    //固定报头
    //控制报文类型
    if(whether)mqtt_txbuf[mqtt_txlen++] = 0x82; //消息类型和标志订阅
    else	mqtt_txbuf[mqtt_txlen++] = 0xA2;    //取消订阅

    //剩余长度
    do
    {
        quint8 encodedByte = DataLen % 128;
        DataLen = DataLen / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( DataLen > 0 )
            encodedByte = encodedByte | 128;
        mqtt_txbuf[mqtt_txlen++] = encodedByte;
    }while ( DataLen > 0 );

    //可变报头
    mqtt_txbuf[mqtt_txlen++] = 0;			//消息标识符 MSB
    mqtt_txbuf[mqtt_txlen++] = 0x0A;        //消息标识符 LSB
    //有效载荷
    mqtt_txbuf[mqtt_txlen++] = BYTE1(topiclen);//主题长度 MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(topiclen);//主题长度 LSB
    memcpy(&mqtt_txbuf[mqtt_txlen],topic,topiclen);
    mqtt_txlen += topiclen;

    if(whether)
    {
       mqtt_txbuf[mqtt_txlen++] = qos;//QoS级别
    }

    ReadData.clear();
    MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);

    //开始事件循环
    StartEvenLoop();

    if(ReadData.length()==0)
    {
        //开始事件循环
        StartEvenLoop();
    }
    memcpy((char *)mqtt_rxbuf,ReadData.data(),ReadData.length());

    if(mqtt_rxbuf[0]==parket_subAck[0] && mqtt_rxbuf[1]==parket_subAck[1]) //订阅成功
    {
        return 0;//订阅成功
    }

    return 1; //失败
}

//MQTT发布数据打包函数
//topic   主题
//message 消息
//qos     消息等级
quint8 MQTT_WorkClass::MQTT_PublishData(char *topic, char *message, quint8 qos)
{
    int topicLength = strlen(topic);
    int messageLength = strlen(message);
    static quint16 id=0;
    int DataLen;
    mqtt_txlen=0;
    //有效载荷的长度这样计算:用固定报头中的剩余长度字段的值减去可变报头的长度
    //QOS为0时没有标识符
    //数据长度             主题名   报文标识符   有效载荷
    if(qos)	DataLen = (2+topicLength) + 2 + messageLength;
    else	DataLen = (2+topicLength) + messageLength;

    //固定报头
    //控制报文类型
    mqtt_txbuf[mqtt_txlen++] = 0x30;    // MQTT Message Type PUBLISH

    //剩余长度
    do
    {
        quint8 encodedByte = DataLen % 128;
        DataLen = DataLen / 128;
        // if there are more data to encode, set the top bit of this byte
        if ( DataLen > 0 )
            encodedByte = encodedByte | 128;
        mqtt_txbuf[mqtt_txlen++] = encodedByte;
    }while ( DataLen > 0 );

    mqtt_txbuf[mqtt_txlen++] = BYTE1(topicLength);//主题长度MSB
    mqtt_txbuf[mqtt_txlen++] = BYTE0(topicLength);//主题长度LSB
    memcpy(&mqtt_txbuf[mqtt_txlen],topic,topicLength);//拷贝主题
    mqtt_txlen += topicLength;

    //报文标识符
    if(qos)
    {
        mqtt_txbuf[mqtt_txlen++] = BYTE1(id);
        mqtt_txbuf[mqtt_txlen++] = BYTE0(id);
        id++;
    }
    memcpy(&mqtt_txbuf[mqtt_txlen],message,messageLength);
    mqtt_txlen += messageLength;

    ReadData.clear();
    MQTT_SendBuf(mqtt_txbuf,mqtt_txlen);

    //开始事件循环
    StartEvenLoop();
    return mqtt_txlen;
}

void MQTT_WorkClass::MQTT_SentHeart(void)
{
    MQTT_SendBuf((quint8 *)parket_heart,sizeof(parket_heart));
}

void MQTT_WorkClass::MQTT_Disconnect(void)
{
    MQTT_SendBuf((quint8 *)parket_disconnet,sizeof(parket_disconnet));
}

void MQTT_WorkClass::MQTT_SendBuf(quint8 *buf,quint16 len)
{
    if(socket_type)
    {
//        qDebug()<<"len:"<<len;
//        for(int i=0;i<len;i++)
//        {
//            qDebug("%#x ",buf[i]);
//        }
       LocalTcpClientSocket->write((const char *)buf,len);
    }
}


//客户端模式:创建客户端
void MQTT_WorkClass::ConnectMqttServer(QString ip,quint16 port)
{
    if(LocalTcpClientSocket)
    {
        LocalTcpClientSocket->close();
        delete  LocalTcpClientSocket;
        LocalTcpClientSocket=nullptr;
    }
    /*1. 创建本地客户端TCP套接字*/
    LocalTcpClientSocket = new QTcpSocket;
    /*2. 设置服务器IP地址*/
    QHostAddress FarServerAddr(ip);
    /*3. 连接客户端的信号槽*/
    connect(LocalTcpClientSocket,SIGNAL(connected()),this,SLOT(LocalTcpClientConnectedSlot()));
    connect(LocalTcpClientSocket,SIGNAL(disconnected()),this,SLOT(LocalTcpClientDisconnectedSlot()));
    connect(LocalTcpClientSocket,SIGNAL(readyRead()),this,SLOT(LocalTcpClientReadDtatSlot()));
    connect(LocalTcpClientSocket,SIGNAL(bytesWritten(qint64)),this,SLOT(LocalTcpClientBytesWrittenSlot(qint64)));

    /*4. 尝试连接服务器主机*/
    LocalTcpClientSocket->connectToHost(FarServerAddr,port);
}

void MQTT_WorkClass::Set_MQTT_Addr(QString ip,quint16 port,QString MQTT_ClientID,QString MQTT_UserName,QString MQTT_PassWord)
{
    m_ip=ip;
    m_port=port;
    m_MQTT_ClientID=MQTT_ClientID;
    m_MQTT_UserName=MQTT_UserName;
    m_MQTT_PassWord=MQTT_PassWord;
}


//客户端模式:响应连接上服务器之后的操作
void MQTT_WorkClass::LocalTcpClientConnectedSlot()
{
    socket_type=1;
    //通知外部
    emit MQTT_ConnectState(socket_type);
    //结束事件循环
    EndEvenLoop();
}

//客户端模式:断开服务器
void MQTT_WorkClass::LocalTcpClientDisconnectedSlot()
{
    socket_type=0;

    //通知外部
    emit MQTT_ConnectState(socket_type);
}


//客户端模式:读取服务器发过来的数据
void MQTT_WorkClass::LocalTcpClientReadDtatSlot()
{
   ReadData=LocalTcpClientSocket->readAll();
   //qDebug()<<"读取服务器发过来的数据:"<<ReadData.length();
   //qDebug()<<"读取服务器发过来的数据:"<<ReadData;
   //这里发布一个信号,这样在外面就可以连接槽函数,解析数据做出动作
   emit ReceiveWechatData(ReadData);
   EndEvenLoop(); //退出事件循环
}

//客户端模式:数据发送成功
void MQTT_WorkClass::LocalTcpClientBytesWrittenSlot(qint64 byte)
{
    LogSend(QString("数据发送成功:%1\n").arg(byte));
    EndEvenLoop(); //退出事件循环
}

//订阅主题
void MQTT_WorkClass::slot_SubscribeTopic(QString topic)
{
    if(MQTT_SubscribeTopic(topic.toUtf8().data(),0,1))
    {
        LogSend(QString("主题订阅失败.\n"));
    }
    else
    {
         LogSend(QString("主题订阅成功.\n"));
    }
}

//发布消息
void MQTT_WorkClass::slot_PublishData(QString topic,QString message)
{
     MQTT_PublishData(topic.toUtf8().data(),message.toUtf8().data(),0);
}


void MQTT_WorkClass::EndEvenLoop()
{
    //停止定时器
    timer->stop();
    //先退出事件循环
    loop.exit();
    //qDebug()<<"退出事件循环";
}


//开始事件循环
void MQTT_WorkClass::StartEvenLoop()
{
    //qDebug()<<"开始事件循环";
    timer->start(5000);
    loop.exec();
}


//断开连接
void MQTT_WorkClass::slot_tcp_close()
{
    if(socket_type)
    {
        timer->stop();
        loop.exit();
        LocalTcpClientSocket->close();
    }
}

  之后是笔者自己改动一些判断逻辑:

// 新建Mqtt服务端对象
    mqttClient = new MQTT_WorkClass(this);

// 连接
void MainWindow::on_connect_clicked()
{
    serverIp = ui->addr->text();
    serverPort = ui->port->text().toUShort();
    topicToSubscribe = ui->sub->text();
    topicToPublish = ui->pub->text();
    qDebug() << "链接开始...";
    mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
    mqttClient->run();
    mqttClient->slot_SubscribeTopic(topicToSubscribe);

    // 连接信号与槽,避免重复连接
    disconnect(mqttClient, SIGNAL(MQTT_ConnectState(bool)), this, SLOT(reconnect(bool)));
    connect(mqttClient, SIGNAL(MQTT_ConnectState(bool)), this, SLOT(reconnect(bool)));
    connect(mqttClient, SIGNAL(ReceiveWechatData(QByteArray)), this, SLOT(receiveWechatData(QByteArray)));
    connectStatus = true;
}

void MainWindow::receiveMessege_Slot(const QByteArray &buf, const QMqttTopicName &topic)
{
    QString str = topic.name() + ": " + QString(buf);
    ui->receiveEdit->appendPlainText(str);
}

// 断线重连
void MainWindow::reconnect(bool)
{
    // 避免多次连接时重复创建mqttClient
    if (mqttClient) {
        delete mqttClient;
    }
    // 读取配置
    mqttClient = new MQTT_WorkClass(this);
    mqttClient->Set_MQTT_Addr(serverIp, serverPort, clientId, username, password);
    mqttClient->run();
    mqttClient->slot_SubscribeTopic(topicToSubscribe);
}

// 打包 bool 类型
QJsonObject MainWindow::bcreateJsonObj(const QMap<QString, bool>& dataMap) {  
    QJsonObject rootObject;
    rootObject["id"] = "123";

    QJsonObject paramsObject;
    for (auto it = dataMap.constBegin(); it != dataMap.constEnd(); ++it) {
        QJsonObject valueObj;
        valueObj["value"] = it.value(); // 直接使用 bool 值
        paramsObject[it.key()] = valueObj;
    }
    rootObject["params"] = paramsObject;

    return rootObject;
}

// 解包
void MainWindow::parseJsonAndAction(const QByteArray& jsonData) {
    QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData);
    if (jsonDoc.isNull() || !jsonDoc.isObject()) {
        qDebug() << "JSON解析失败或不是一个对象";
        return;
    }

    QJsonObject jsonObject = jsonDoc.object();
    if (jsonObject.contains("params")) {
        QJsonObject paramsObject = jsonObject["params"].toObject();

        // 更新LED状态
        if (paramsObject.contains("led")) {
            isled = paramsObject["led"].toBool();
            ui->receiveEdit->appendPlainText(isled ? "led on" : "led off");
        }

        // 更新电机状态
        if (paramsObject.contains("motor")) {
            ismotor = paramsObject["motor"].toBool();
            ui->receiveEdit->appendPlainText(ismotor ? "motor on" : "motor off");
        }

        // 更新监控状态
        if (paramsObject.contains("monitor")) {
            ismonitor = paramsObject["monitor"].toBool();
            ui->receiveEdit->appendPlainText(ismonitor ? "monitor on" : "monitor off");
        }

        update();  // 重新绘制界面
    } else {
        qDebug() << "JSON对象中不包含params";
    }
}

2.2 音乐播放器的实现

  这部分讲道理比较简单,网上的资料也很多,ui部分的设计大家可以自定义,详细的实现代码这里就直接给出了。

// MusicSelectDialog.h
namespace Ui {
class MusicSelectDialog;
}

class MusicSelectDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MusicSelectDialog(QWidget *parent = nullptr);
    ~MusicSelectDialog();
    QString getSelectedMusic() const;
    QStringList getMusicList() const; // 新增这个方法

protected:
    void paintEvent(QPaintEvent *event) override; // 声明 paintEvent 方法

private slots:
    void on_musicListWidget_itemDoubleClicked(QListWidgetItem *item);

private:
    Ui::MusicSelectDialog *ui;  // ui 指针的声明
    QStringList musicFiles; // 新增一个成员变量存储音乐文件
};

// MusicSelectDialog.cpp
MusicSelectDialog::MusicSelectDialog(QWidget *parent) :
    QDialog(parent),
    ui(new Ui::MusicSelectDialog)
{
    ui->setupUi(this);
    setWindowTitle("播放列表");
    setAttribute(Qt::WA_OpaquePaintEvent); // 添加这一行
    setAttribute(Qt::WA_NoSystemBackground, true);
    setAttribute(Qt::WA_OpaquePaintEvent, true);
    setStyleSheet("background-color: rgba(255, 255, 255, 0);"); // 透明背景

    // 获取音乐文件列表
    QDir musicDir("音乐文件的绝对路径");

    if (musicDir.exists()) {
        musicFiles = musicDir.entryList(QStringList() << "*.mp3" << "*.wav" << "*.ogg", QDir::Files);
        ui->musicListWidget->addItems(musicFiles);
    }

    connect(ui->musicListWidget, &QListWidget::itemDoubleClicked, this, &MusicSelectDialog::on_musicListWidget_itemDoubleClicked);
}

QString MusicSelectDialog::getSelectedMusic() const
{
    if (ui->musicListWidget->currentItem()) {
        QString fileName = ui->musicListWidget->currentItem()->text();
        QDir musicDir("音乐文件的绝对路径");
        return musicDir.absoluteFilePath(fileName); // 返回绝对路径
    }
    return QString();
}
void MusicSelectDialog::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.fillRect(rect(), Qt::white);
    painter.setOpacity(0.9);
    painter.drawPixmap(0, 0, width(), height(), QPixmap("背景图片的地址"));
    painter.setOpacity(1);
}

  这样就有个播放器的大概框架了,我们已经成功读取到了音乐的列表,接下来就是播放播放。


// 设置音频格式
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
			...
			
    QAudioFormat format;
    format.setSampleRate(44100);
    format.setChannelCount(2);
    format.setSampleSize(16);
    format.setCodec("audio/pcm");

    // 创建 QMediaPlayer
    mediaPlayer = new QMediaPlayer(this);
    mediaPlayer->setVolume(50); // 设置默认音量
    isPlaying = false;
    connect(ui->soundSlider, &QSlider::valueChanged, this, &MainWindow::setVolume);

    // 初始化音乐列表
    MusicSelectDialog musicDialog(this);
    musicList = musicDialog.getMusicList(); // 获取音乐列表
 }
 
// 播放音乐,本质是对mediaPlayer->play()的封装,也可以直接使用这个
void MainWindow::playCurrentMusic()
{
    // 定义音乐目录
    QDir musicDir("绝对地址");
    // 获取当前音乐的文件名
    QString musicFileName = musicList[currentIndex]; // 从列表中获取文件名
    QString musicPath = musicDir.absoluteFilePath(musicFileName); // 生成绝对路径

    qDebug() << "Attempting to play music from path:" << musicPath; // 调试信息

    // 确认文件是否存在
    if (QFile::exists(musicPath)) {
        mediaPlayer->setMedia(QUrl::fromLocalFile(musicPath)); // 设置新歌曲
        mediaPlayer->play(); // 播放新歌曲
        isPlaying = true; // 更新播放状态为正在播放

        // 更新文本框显示当前播放的文件名
        ui->Songname->setPlainText(musicFileName); // 只显示文件名
        qDebug() << "Now playing:" << musicFileName; // 打印当前播放文件名
    } else {
        qDebug() << "File not found:" << musicPath; // 文件未找到
    }
}

2.3 虚拟键盘

  这部分资料也很多,大部分的代码都是重复的,比如你每一个键值都要设置,像视频里面的那种下来得设置上百行且都是没有意义的重复,这里就给一个简单框架吧。

// VirtualKeyboard.h
#ifndef VIRTUALKEYBOARD_H
#define VIRTUALKEYBOARD_H

#include <QWidget>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QLineEdit>

class VirtualKeyboard : public QWidget
{
    Q_OBJECT

public:
    explicit VirtualKeyboard(QWidget *parent = nullptr);
    ~VirtualKeyboard();

signals:
    void textInput(const QString &text);

private slots:
    void onButtonClicked();

private:
    void createLayout();
    QLineEdit *display;  // 用于显示输入的文本
};

#endif // VIRTUALKEYBOARD_H

// VirtualKeyboard.cpp
#include "VirtualKeyboard.h"

VirtualKeyboard::VirtualKeyboard(QWidget *parent)
    : QWidget(parent), display(new QLineEdit(this))
{
    createLayout();
}

VirtualKeyboard::~VirtualKeyboard() {}

void VirtualKeyboard::createLayout()
{
    // 设置显示区域
    display->setReadOnly(true);  // 设置为只读
    display->setAlignment(Qt::AlignRight);

    // 按钮布局
    QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addWidget(display);

    // 创建按键行
    QStringList rows = {
        "1 2 3", "4 5 6", "7 8 9", "0 Q W E"
    };

    foreach (const QString &row, rows) {
        QHBoxLayout *buttonLayout = new QHBoxLayout();
        QStringList keys = row.split(' ');

        foreach (const QString &key, keys) {
            QPushButton *button = new QPushButton(key, this);
            connect(button, &QPushButton::clicked, this, &VirtualKeyboard::onButtonClicked);
            buttonLayout->addWidget(button);
        }

        mainLayout->addLayout(buttonLayout);
    }

    setLayout(mainLayout);
}

void VirtualKeyboard::onButtonClicked()
{
    QPushButton *button = qobject_cast<QPushButton *>(sender());
    if (button) {
        QString text = button->text();
        display->setText(display->text() + text);  // 更新显示区域
        emit textInput(display->text());  // 发出文本输入信号
    }
}

  之后在Mainwindow.cpp中引用即可。

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), inputField(new QLineEdit(this)), virtualKeyboard(new VirtualKeyboard(this))
{
    // 设置输入框
    inputField->setPlaceholderText("请输入文本...");
    inputField->setGeometry(50, 50, 300, 40);
    inputField->setReadOnly(true);

    // 创建虚拟键盘
    virtualKeyboard->setGeometry(50, 100, 400, 300);
    virtualKeyboard->hide();  // 初始时隐藏虚拟键盘

    // 按下输入框时显示虚拟键盘
    connect(inputField, &QLineEdit::editingFinished, this, [=]() {
        virtualKeyboard->show();
    });

    // 更新显示的文本
    connect(virtualKeyboard, &VirtualKeyboard::textInput, this, [=](const QString &text) {
        inputField->setText(text);
    });
}

三、问题杂谈

  很感谢你已经看到这里,接下来是笔者踩的一些坑,希望对你有些帮助:

  1. 关于qt平台的问题:笔者是再Windows编辑,然后在Ubuntu虚拟机下交叉编译,最后到开发板上。这里我建议给开发板大家刷Ubuntu系统(尤其是泰山派,由于buildroot下的qt是直接集成到系统里了,导致你后续添加库很麻烦,比如qtmqtt),这样你只需要每次将在虚拟机下的环境拷贝一下给开发板就可以了,不需要额外编译环境。
  2. 关于QtMqtt环境问题:这里可以参考mqtt客户端设计,Ubuntu端的可以参考Ubuntu全过程
  3. 关于Linux开发板上界面乱码:这里需要我们去移植一下字库即可,这部分挺简单的大家去搜即可,注意要和编辑qt时的字体一样。
  4. 关于界面冲突:可以利用sudo systemctl set-default multi-user.target禁用,但是会导致鼠标键盘等tty也禁用,这时候再利用sudo systemctl restart udev即可恢复正常。
  5. 关于蓝牙连接问题:这里最好别用root用户操作,容易报错,主要是利用 bluetoothctl 工具。
    步骤1:安装蓝牙相关的包:
sudo apt update
sudo apt install bluez bluez-tools pulseaudio-module-bluetooth pavucontrol

步骤2:启动蓝牙服务

sudo systemctl enable bluetooth
sudo systemctl start bluetooth

步骤3:连接蓝牙

bluetoothctl                  # 使用 bluetoothctl 工具来检查蓝牙适配器状态
#指令
power on                      # 打开蓝牙。
agent on                      # 启用代理,允许设备自动连接。
scan on                       # 扫描附近的蓝牙设备。
pair <device_address>         # 配对一个设备。
connect <device_address>      # 连接设备。
exit				          # 退出 bluetoothctl。

如果实在不行可以参考这篇博客ubuntu连接蓝牙,其余问题可以留言或者评论,笔者看见会及时解答。


免责声明

  本次项目的部分代码也是参考了很多优秀作者开源的项目,再次感谢,如有侵权可联系笔者。

标签:毕设,txlen,QT,void,智能家居,mqtt,MQTT,QString,txbuf
From: https://blog.csdn.net/sincerelover/article/details/143515162

相关文章

  • 基于QT的桌面软件,就是要比winform、wpf体验好。
    QT具有跨平台性强、可定制程度高等优点,能在多种操作系统上运行,并且对于开发者来说提供了丰富的功能库。然而,WinForm开发相对简单快捷,适合快速构建小型应用。WPF则在界面设计和动画效果方面表现出色,能创造出非常美观的用户界面。不同的开发场景和需求会决定哪种技术更合适......
  • QT creator 中c和c++混编问题
    今天在编译包含.c和.cpp的QT项目,在整合各种代码的时候,碰到一些问题,为了方便后查,初步总结如下:1.新版QT中一般使用g++编译cpp文件和c文件,可以在项目中同时使用c文件和cpp文件。后缀cpp文件g++自动识别为c++代码自动进行c++编译,后缀c文件自动识别为c代码进行c编译。这个时候必须注意......
  • 远翔升压恒流芯片FP7209X与FP7209M什么区别?做以下应用市场摄影补光灯、便携灯、智能家
    一,概述FP7209是台湾远翔一款非同步升压LED驱动IC,封装有2种,分别是SOP-8L(EP),TSSOP-14L(EP)。控制外部开关NMOS。输入低启动电压2.8V,可支持单节锂电池供电。工作电压5V,VFB反馈电压0.25V,反馈电压低,取样电阻功率损耗也降低,整体转换效率提升。软启动时间透过外部电容调整,LED开路保......
  • 《古剑奇谭网络版》qt5widgets.dll文件丢失全方位解决方法大全
    《古剑奇谭网络版》是一款大型多人在线角色扮演游戏。如果在运行该游戏时遇到qt5widgets.dll文件丢失的错误提示,这通常意味着你的系统缺少了Qt框架中的一个必要组件。Qt是一个跨平台的应用程序开发框架,广泛用于图形用户界面(GUI)的创建。要解决qt5widgets.dll文件丢失的问题,你可......
  • Qt入门基础分享
    文章目录学习Qt语言之前的基本知识1.编程基础语法:面向对象编程(OOP):基本数据结构:了解数组、链表、栈、队列、树(如二叉树、平衡树)、图(如邻接矩阵、邻接表)等。算法:熟悉常见的排序算法(如快速排序、归并排序、冒泡排序)和查找算法(如线性查找、二分查找)。复杂度......
  • node.js毕设中国传统文化服饰交流平台(程序+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于中国传统文化服饰交流平台的研究,现有研究主要集中在传统文化服饰的历史、艺术价值等方面,如在传统服饰的审美意蕴研究中,多是从单一的美学角度探讨其......
  • node.js毕设文物藏品管理系统(程序+论文)
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景关于文物藏品管理系统的研究,现有研究主要集中在藏品的数字化管理、RFID技术在藏品管理中的应用等方面。例如,一些研究关注了如何利用RFID技术实现藏品的......
  • php购物商城在线购物系统php毕业设计php课程设计毕设指导计算机源码获取php源码获取电
    风筝一,功能介绍PHP➕MySQL前台功能:登录:用户可以通过输入用户名和密码进行身份验证,登录成功后进入个人中心页面,享受个性化服务。注册:新用户可以通过填写必要信息(如用户名、密码、邮箱等)完成注册,注册成功后即可登录并开始使用平台服务。轮播图:首页展示一系列动态轮播图,......
  • Qt多线程- QThread 创建多线程程序
    QThread创建多线程程序QThread类功能简介今天说一下Qt中的多线程。QThread类不依赖平台的管理线程的方法。一个QThread类的对象管理一个线程,一般从QThread继承一个自定义类,并重新定义虚函数run(),在run()函数里实现线程需要完成的任务。将应用程序的线程称为......
  • 基于SpringBoot+Vue的库存管理系统设计与实现毕设(文档+源码)
            目录一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、源码获取:        大家好呀,我是一个混迹在java圈的码农。今天要和大家分享的是一款基于SpringBoot+Vue的库存试管理系统,项目源码请点击文章末尾联系我哦~目前有各类成品......