文章目录
前言
前段时间,笔者觉得以前的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);
});
}
三、问题杂谈
很感谢你已经看到这里,接下来是笔者踩的一些坑,希望对你有些帮助:
- 关于qt平台的问题:笔者是再Windows编辑,然后在Ubuntu虚拟机下交叉编译,最后到开发板上。这里我建议给开发板大家刷Ubuntu系统(尤其是泰山派,由于buildroot下的qt是直接集成到系统里了,导致你后续添加库很麻烦,比如qtmqtt),这样你只需要每次将在虚拟机下的环境拷贝一下给开发板就可以了,不需要额外编译环境。
- 关于QtMqtt环境问题:这里可以参考mqtt客户端设计,Ubuntu端的可以参考Ubuntu全过程。
- 关于Linux开发板上界面乱码:这里需要我们去移植一下字库即可,这部分挺简单的大家去搜即可,注意要和编辑qt时的字体一样。
- 关于界面冲突:可以利用
sudo systemctl set-default multi-user.target
禁用,但是会导致鼠标键盘等tty也禁用,这时候再利用sudo systemctl restart udev
即可恢复正常。 - 关于蓝牙连接问题:这里最好别用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