Qt u盘自动升级软件
Qt u盘自动升级软件
- Chapter1 Qt u盘自动升级软件
- u盘自动升级软件思路:
- step1. 获取U盘 判断U盘名字是否正确, 升级文件是否存在。
- step2. 升级
- step3. 升级界面
- Chapter2 Qt 嵌入式设备应用程序,通过U盘升级的一种思路
- Chapter3 在开发板上运行的QT应用程序,如何拷贝文件到U盘
- Chapter4 嵌入式Qt,U盘升级程序
- 前言
- 一、实现过程
- 1.检测U盘中的更新程序
- 2. 数字验证
- 3.升级程序
- 4.主函数
- 二、总结
Chapter1 Qt u盘自动升级软件
原文链接
u盘自动升级软件思路:
1.检测U盘是否存在
2.检测升级文件是否存在,升级文件版本是否比当前软件版本新
3.软件升级
step1. 获取U盘 判断U盘名字是否正确, 升级文件是否存在。
//获取U盘 判断U盘名字是否正确, 升级文件是否存在。
void MainWindow::getUDisk()
{
QMap<QString,QString> namePath;
foreach (const QStorageInfo &storage, QStorageInfo::mountedVolumes())
{
if (storage.isValid() && storage.isReady())
{
UDiskPath = storage.rootPath();
namePath.insert(storage.displayName(),storage.rootPath());
}
}
QString path = namePath.value("LADYBUG");
QFile file(path+"/"+app::updateFile);
if(file.exists())
{
//存在 弹窗是否升级
}
else
{
//不存在
}
}
step2. 升级
OperateThread *thread = new OperateThread;
thread->setUpgradeFile(strSrcFile);
ShowProgressDialog dialog(this);
connect(thread, SIGNAL(emitFinish(int)), &dialog, SLOT(onThreadFinished(int)));
thread->start();
dialog.exec();
step3. 升级界面
class ShowProgressDialog : public QDialog
{
Q_OBJECT
public:
explicit ShowProgressDialog(QWidget *parent = 0);
~ShowProgressDialog();
void setTitleText(const QString &strText);
void setMessageText(const QString &strMsg);
public slots:
void on_btnOk_clicked();
void onThreadFinished(int nExitCode);
private:
QLabel *m_labelTitle;
QLabel *m_labelMsg;
QPushButton *m_btnOk;
QLabel *m_labelWait;
QMovie *m_pMovie;
};
ShowProgressDialog::ShowProgressDialog(QWidget *parent) : QDialog(parent)
{
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Dialog);
setAttribute(Qt::WA_X11DoNotAcceptFocus);
setFocusPolicy(Qt::NoFocus);
//更改背景色
QPalette palette = this->palette();
QPixmap pix(":/res/dialog_bg.png");
palette.setBrush(QPalette::Background,QBrush(pix));
setAutoFillBackground(true);
this->setPalette(palette);
resize(410, 260);
setMinimumSize(QSize(410, 260));
setMaximumSize(QSize(410, 260));
m_labelTitle = new QLabel(this);
m_labelMsg = new QLabel(this);
m_btnOk = new QPushButton(this);
m_labelWait = new QLabel(this);
m_pMovie = new QMovie(":/res/loading.gif");
m_labelTitle->setGeometry(QRect(10, 1, 220, 30));
m_labelMsg->setGeometry(QRect(30, 60, 350, 60));
m_labelMsg->setWordWrap(true);
m_btnOk->setGeometry(QRect(320, 180, 58, 58));
m_labelWait->setGeometry(QRect(30, 120, 322, 18));
m_labelWait->setMovie(m_pMovie);
m_pMovie->start();
m_labelTitle->setStyleSheet("font: bold 17px; color: white;");
m_labelMsg->setStyleSheet("font: bold 17px ; color: black; ");
m_btnOk->setStyleSheet("QPushButton{background-image: url(:/res/dialog_ok.png);border: 0px;}"
"QPushButton:pressed{background-image: url(:/res/dialog_ok_p.png);border: 0px;}");
connect(m_btnOk , SIGNAL(clicked()) , this , SLOT(on_btnOk_clicked()));
setTitleText("升级");
setMessageText("正在升级,请勿插拔U盘!");
m_btnOk->hide();
}
ShowProgressDialog::~ShowProgressDialog()
{
delete m_labelTitle;
delete m_labelMsg;
delete m_btnOk;
}
void ShowProgressDialog::setTitleText(const QString &strText)
{
m_labelTitle->setText(strText);
}
void ShowProgressDialog::setMessageText(const QString &strMsg)
{
m_labelMsg->setText(strMsg);
}
void ShowProgressDialog::on_btnOk_clicked()
{
QDialog::accept();
}
void ShowProgressDialog::onThreadFinished(int nExitCode)
{
qDebug() << "nExitCode" << nExitCode;
if (nExitCode == 0)
{
setMessageText("升级成功,请重新上电。");
m_btnOk->show();
m_pMovie->stop();
}
else
{
setMessageText("升级出错!请断电以恢复!");
m_btnOk->show();
m_pMovie->stop();
}
}
注意:线程,继承自QThread,完成复制工作。使用QProcess来完成Linux下的解压和删除的工作。关于Qt的多线程,可以去查找一下其他的教程,这里就不做过多的解释。
class OperateThread : public QThread
{
Q_OBJECT
public:
explicit OperateThread(QObject *parent = 0);
public:
void setUpgradeFile(QString strUpgradeFile)
{
m_UpgradeFile = strUpgradeFile;
}
protected:
void run();
signals:
void emitFinish(int);
public slots:
private:
void OprUpgradeUp();
private:
QString m_UpgradeFile;
};
OperateThread::OperateThread(QObject *parent) : QThread(parent)
{
}
void OperateThread::run()
{
//很复杂的数据处理
OprUpgradeUp();
}
void OperateThread::OprUpgradeUp()
{
#define DEST_DIR "../"
QString destDir = QString("%1/update.tar.gz").arg(DEST_DIR);
if( QFile::copy(m_UpgradeFile, destDir) )
{
qDebug()<<"-------成功-----------";
}
else
{
qDebug()<<"-------出错-----------";
//升级失败
emit emitFinish(1);
return;
}
#ifdef Q_OS_LINUX
QString exe = QString("tar -zxvf %1 -C %2").arg(destDir).arg(DEST_DIR);
QProcess::execute(exe);
//升级成功,自动同步磁盘并删除ARM上的升级包
exe = QString("sync");
QProcess::execute(exe);
exe = QString("rm -rf %1").arg(destDir);
QProcess::execute(exe);
#endif
//升级成功
emit emitFinish(0);
}
Chapter2 Qt 嵌入式设备应用程序,通过U盘升级的一种思路
原文链接:https://blog.csdn.net/qq1113231395/article/details/81867153
最近在做一个通过U盘升级的功能,程序是运行在ARM Linux Qt平台上的。这个应该是很多嵌入式设备必备的一个功能了,所以把这部分的实现抽出来,做成一个例子供需要的人参考。这只是U盘升级的一种思路,如果有更好的方法,也可以提供相应的意见。
源码下载:softwareupgrade.tar.gz
升级文件的格式是通过tar压缩后的文件以gz结尾的, 可以通过tar命令生成相应的升级文件如update.tar.gz:
tar -czvf update.tar.gz demo
主要思路就是,点击相应的功能后从U盘中选中需要升级的文件update.tar.gz,之后开启一个线程和一个提示正在升级的对话框,以免让用户觉得假死。在线程中完成的事情是:将选中的升级文件复制到应用程序的目录中,然后将update.tar.gz 解压覆盖原来的程序。最后将update.tar.gz删除。发送一个线程结束的信号。
下面就介绍一下实现过程。
MainWindow很简单,只有一个按钮。 点击按钮开启线程,显示提示框,绑定线程结束后的信号。提示框根据信号的参数值来确定升级是否成功。
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
QString strSrcFile = QFileDialog::getOpenFileName(this, "选择升级文件", ".", "tar (*.gz)");
qDebug() << "strSrcFile" << strSrcFile;
OperateThread *thread = new OperateThread;
thread->setUpgradeFile(strSrcFile);
ShowProgressDialog dialog(this);
connect(thread, SIGNAL(emitFinish(int)), &dialog, SLOT(onThreadFinished(int)));
thread->start();
dialog.exec();
}
运行的效果图:
提示框: void onThreadFinished(int nExitCode) 线程结束后更新提示框。里面还使用到了一些qss,来设置背景。
class ShowProgressDialog : public QDialog
{
Q_OBJECT
public:
explicit ShowProgressDialog(QWidget *parent = 0);
~ShowProgressDialog();
void setTitleText(const QString &strText);
void setMessageText(const QString &strMsg);
public slots:
void on_btnOk_clicked();
void onThreadFinished(int nExitCode);
private:
QLabel *m_labelTitle;
QLabel *m_labelMsg;
QPushButton *m_btnOk;
QLabel *m_labelWait;
QMovie *m_pMovie;
};
ShowProgressDialog::ShowProgressDialog(QWidget *parent) : QDialog(parent)
{
setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint | Qt::Dialog);
setAttribute(Qt::WA_X11DoNotAcceptFocus);
setFocusPolicy(Qt::NoFocus);
//更改背景色
QPalette palette = this->palette();
QPixmap pix(":/res/dialog_bg.png");
palette.setBrush(QPalette::Background,QBrush(pix));
setAutoFillBackground(true);
this->setPalette(palette);
resize(410, 260);
setMinimumSize(QSize(410, 260));
setMaximumSize(QSize(410, 260));
m_labelTitle = new QLabel(this);
m_labelMsg = new QLabel(this);
m_btnOk = new QPushButton(this);
m_labelWait = new QLabel(this);
m_pMovie = new QMovie(":/res/loading.gif");
m_labelTitle->setGeometry(QRect(10, 1, 220, 30));
m_labelMsg->setGeometry(QRect(30, 60, 350, 60));
m_labelMsg->setWordWrap(true);
m_btnOk->setGeometry(QRect(320, 180, 58, 58));
m_labelWait->setGeometry(QRect(30, 120, 322, 18));
m_labelWait->setMovie(m_pMovie);
m_pMovie->start();
m_labelTitle->setStyleSheet("font: bold 17px; color: white;");
m_labelMsg->setStyleSheet("font: bold 17px ; color: black; ");
m_btnOk->setStyleSheet("QPushButton{background-image: url(:/res/dialog_ok.png);border: 0px;}"
"QPushButton:pressed{background-image: url(:/res/dialog_ok_p.png);border: 0px;}");
connect(m_btnOk , SIGNAL(clicked()) , this , SLOT(on_btnOk_clicked()));
setTitleText("升级");
setMessageText("正在升级,请勿插拔U盘!");
m_btnOk->hide();
}
ShowProgressDialog::~ShowProgressDialog()
{
delete m_labelTitle;
delete m_labelMsg;
delete m_btnOk;
}
void ShowProgressDialog::setTitleText(const QString &strText)
{
m_labelTitle->setText(strText);
}
void ShowProgressDialog::setMessageText(const QString &strMsg)
{
m_labelMsg->setText(strMsg);
}
void ShowProgressDialog::on_btnOk_clicked()
{
QDialog::accept();
}
void ShowProgressDialog::onThreadFinished(int nExitCode)
{
qDebug() << "nExitCode" << nExitCode;
if (nExitCode == 0)
{
setMessageText("升级成功,请重新上电。");
m_btnOk->show();
m_pMovie->stop();
}
else
{
setMessageText("升级出错!请断电以恢复!");
m_btnOk->show();
m_pMovie->stop();
}
}
线程,继承自QThread,完成复制工作。使用QProcess来完成Linux下的解压和删除的工作。关于Qt的多线程,可以去查找一下其他的教程,这里就不做过多的解释。
class OperateThread : public QThread
{
Q_OBJECT
public:
explicit OperateThread(QObject *parent = 0);
public:
void setUpgradeFile(QString strUpgradeFile)
{
m_UpgradeFile = strUpgradeFile;
}
protected:
void run();
signals:
void emitFinish(int);
public slots:
private:
void OprUpgradeUp();
private:
QString m_UpgradeFile;
};
OperateThread::OperateThread(QObject *parent) : QThread(parent)
{
}
void OperateThread::run()
{
//很复杂的数据处理
OprUpgradeUp();
}
void OperateThread::OprUpgradeUp()
{
#define DEST_DIR "../"
QString destDir = QString("%1/update.tar.gz").arg(DEST_DIR);
if( QFile::copy(m_UpgradeFile, destDir) )
{
qDebug()<<"-------成功-----------";
}
else
{
qDebug()<<"-------出错-----------";
//升级失败
emit emitFinish(1);
return;
}
#ifdef Q_OS_LINUX
QString exe = QString("tar -zxvf %1 -C %2").arg(destDir).arg(DEST_DIR);
QProcess::execute(exe);
//升级成功,自动同步磁盘并删除ARM上的升级包
exe = QString("sync");
QProcess::execute(exe);
exe = QString("rm -rf %1").arg(destDir);
QProcess::execute(exe);
#endif
//升级成功
emit emitFinish(0);
}
Chapter3 在开发板上运行的QT应用程序,如何拷贝文件到U盘
原文链接:https://blog.csdn.net/pang_fighting/article/details/139114780
笔者最近一直被这个问题所困惑,好在今天已经解决,之前找了很多资料,试了其他家的使用QProcess类来实现QT应用下的命令行实现,但很遗憾还是不行,找到新的解决方法如下:
system("cp -r /home/root/test_data /run/media/sda1");
system("sync");
system("umount /run/media/sda1");
解释一下,这需要你的开发板移植的系统能够插入U盘直接挂载,否则需要先检测U盘插入再挂载U盘,才可以使用,笔者用的正点原子157开发板,使用的是他们的官方系统,这个系统是支持插入U盘直接挂载的,挂载目录是“/run/media/sda1”。
U盘挂载完成之后,使用C函数system来执行命令行拷贝文件,拷贝完成之后执行sync命令,最后直接取消挂载拔出U盘即可!
Chapter4 嵌入式Qt,U盘升级程序
原文链接:https://blog.csdn.net/sjd753/article/details/135284869
前言
近期在完成一个U盘升级功能,对于Arm嵌入式设备来说应该算是必备的了。
参考链接:
https://blog.csdn.net/newnewman80/article/details/8766657
https://blog.csdn.net/newnewman80/article/details/8766657
一、实现过程
1.检测U盘中的更新程序
结合netlink捕获USB的热拔插,我们需要去检测U盘的挂载点,通常情况下都是挂载在/media下。
bool checkConsoleAppExists()
{
DIR *dir = opendir("/run/media"); // 打开 /run/media 目录 具体的挂载点根据设备决定
if (dir)
{
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0)
{
devpath = "/run/media/" + std::string(entry->d_name);
path = "/run/media/" + std::string(entry->d_name) + "/updateFileName";
std::string signatureFilePath = devpath +"/sigFileName.sig";
std::string publicKeyPath = devpath + "/key.pub" ;
if (access(path.c_str(), F_OK) != -1)
{
//检测到更新程序,可以做一些校验,我这里的话是验证数字签名,根据具体情况决定
return checkUpdate(signatureFilePath,publicKeyPath,path);
}
}
}
closedir(dir);
}
return false;
}
2. 数字验证
/*检测是否需要更新,是否为正确的更新程序*/
bool checkUpdate(const std::string& signatureFile, const std::string& publicKeyFile, const std::string& dataFile) {
// 打开更新文件
// 这里是想要去检测版本号,去判断是否需要升级的,后面由于使用了数字签名就没有实现这部分。
FILE *updateFile = fopen(dataFile.c_str(), "rb");
if (!updateFile) {
perror("Error opening update file\n");
return false;
}
// 验证数字签名
return verifySignature(signatureFile,publicKeyFile,dataFile);
}
// 函数用于验证数字签名是否有效
bool verifySignature(const std::string& signatureFile, const std::string& publicKeyFile, const std::string& dataFile)
{
// 打开公钥文件以读取公钥信息
FILE* fp = fopen(publicKeyFile.c_str(), "r");
if (!fp) {
std::cerr << "Error opening public key file." << std::endl;
return false;
}
// 从公钥文件中读取 RSA 公钥信息
RSA* rsa = PEM_read_RSA_PUBKEY(fp, NULL, NULL, NULL);
fclose(fp);
// 检查是否成功读取公钥信息
if (!rsa) {
std::cerr << "Error reading public key." << std::endl;
return false;
}
// 创建一个用于存储 RSA 公钥的 EVP_PKEY 对象
EVP_PKEY* evpKey = EVP_PKEY_new();
// 将 RSA 公钥赋值给 EVP_PKEY 对象
if (!EVP_PKEY_assign_RSA(evpKey, rsa)) {
std::cerr << "Error assigning RSA key." << std::endl;
RSA_free(rsa);
return false;
}
// 创建一个用于消息摘要计算的 EVP_MD_CTX 对象
EVP_MD_CTX* ctx = EVP_MD_CTX_new();
if (!ctx) {
std::cerr << "Error creating context." << std::endl;
RSA_free(rsa);
return false;
}
// 打开数据文件以进行签名验证
std::ifstream fileStream(dataFile, std::ios::binary | std::ios::ate);
if (!fileStream.is_open()) {
std::cerr << "Error opening data file." << std::endl;
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
// 读取数据文件的大小和内容
std::streamsize fileSize = fileStream.tellg();
fileStream.seekg(0, std::ios::beg);
std::vector<unsigned char> fileData(fileSize);
if (!fileStream.read(reinterpret_cast<char*>(fileData.data()), fileSize)) {
std::cerr << "Error reading data file." << std::endl;
fileStream.close();
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
fileStream.close();
// 打开签名文件以进行签名验证
std::ifstream signatureFileStream(signatureFile, std::ios::binary);
if (!signatureFileStream.is_open()) {
std::cerr << "Error opening signature file." << std::endl;
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
// 读取签名文件的大小和内容
signatureFileStream.seekg(0, std::ios::end);
size_t signatureFileSize = signatureFileStream.tellg();
signatureFileStream.seekg(0, std::ios::beg);
std::vector<unsigned char> signatureData(signatureFileSize);
if (!signatureFileStream.read(reinterpret_cast<char*>(signatureData.data()), signatureFileSize)) {
std::cerr << "Error reading signature file." << std::endl;
signatureFileStream.close();
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
signatureFileStream.close();
// 更新消息摘要的内容,计算数据文件的哈希值
if (!EVP_VerifyUpdate(ctx, fileData.data(), fileSize)) {
std::cerr << "Error updating verification." << std::endl;
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
return false;
}
// 验证签名的有效性
int result = EVP_VerifyFinal(ctx, signatureData.data(), signatureFileSize, evpKey);
EVP_MD_CTX_free(ctx);
RSA_free(rsa);
// 根据验证结果返回相应的布尔值
if (result != 1) {
std::cerr << "Signature verification failed." << std::endl;
return false;
}
std::cout << "Signature verification successful." << std::endl;
return true;
}
3.升级程序
升级程序的话,实际上就是一个拷贝的过程。将设备上的备份,U盘中的更新程序拷贝出来。
/* 更新程序 */
bool copyFile(const char *sourcePath, const char *destinationPath) {
//system("mv updateFileName updateFileName.back"); //这里是一个备份,后续可以进行一个升级失败,备份还原。
FILE *sourceFile = fopen(sourcePath, "rb");
FILE *destinationFile = fopen(destinationPath, "wb");
if (sourceFile == NULL) {
printf("Error opening sourceFile files.\n");
return false;
}
if(destinationFile == NULL){
printf("Error opening destinationFile files.\n");
return false;
}
char buffer[1024];
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), sourceFile)) > 0) {
printf("start copy file........................\n");
fwrite(buffer, 1, bytesRead, destinationFile);
}
/*更简单一点就是用system命令,直接拷贝
不过有一点需要注意,程序在运行的时候,有时候会拷贝失败。
可以先用system命令删除或者备份,然后再进行拷贝,再用system给个权限。
*/
fclose(sourceFile);
fclose(destinationFile);
return true;
}
4.主函数
int main(void)
{
struct sockaddr_nl client;
struct timeval tv;
int CppLive, rcvlen, ret;
fd_set fds;
int buffersize = 1024;
CppLive = socket(AF_NETLINK, SOCK_RAW, NETLINK_KOBJECT_UEVENT);
memset(&client, 0, sizeof(client));
client.nl_family = AF_NETLINK;
client.nl_pid = getpid();
client.nl_groups = 1; /* receive broadcast message*/
setsockopt(CppLive, SOL_SOCKET, SO_RCVBUF, &buffersize, sizeof(buffersize));
bind(CppLive, (struct sockaddr*)&client, sizeof(client));
while (1) {
char buf[UEVENT_BUFFER_SIZE] = { 0 };
FD_ZERO(&fds);
FD_SET(CppLive, &fds);
tv.tv_sec = 0;
tv.tv_usec = 100 * 1000;
ret = select(CppLive + 1, &fds, NULL, NULL, &tv);
if(ret < 0)
continue;
if(!(ret > 0 && FD_ISSET(CppLive, &fds)))
continue;
/* receive data */
rcvlen = recv(CppLive, &buf, sizeof(buf), 0);
if (rcvlen > 0) {
printf("%s\n", buf);
//检测更新程序
//升级程序
//重启....
}
}
close(CppLive);
return 0;
}
二、总结
以上就是今天要讲的内容,本文仅仅简单介绍了结合netlink的u盘升级,后期可以通过Qt实现U盘升级,实现一个程序,通过界面可以判断更新状态。