#ifndef OTAUPDATE_H
#define OTAUPDATE_H
#include <QObject>
#include <QVariantMap>
#include <QCryptographicHash>
#include <QTimer>
#ifdef IS_REPORT_SPEED
#include <QElapsedTimer>
#endif
class OtaUpdate : public QObject
{
Q_OBJECT
Q_PROPERTY(bool upgrade READ upgrade WRITE setUpgrade NOTIFY upgradeChanged)
Q_PROPERTY(int downloadPrg READ downloadPrg WRITE setDownloadPrg NOTIFY downloadPrgChanged)
Q_PROPERTY(int status READ status WRITE setStatus NOTIFY statusChanged)
Q_PROPERTY(QString speed READ speed WRITE setSpeed NOTIFY speedChanged)
Q_PROPERTY(QString fileSize READ fileSize WRITE setFileSize NOTIFY fileSizeChanged)
Q_PROPERTY(QString remainTime READ remainTime WRITE setRemainTime NOTIFY remainTimeChanged)
Q_PROPERTY(int upgradePrg READ upgradePrg WRITE setUpgradePrg NOTIFY upgradePrgChanged)
Q_PROPERTY(bool speedDisp READ speedDisp CONSTANT)
public:
enum OtaType {
FullPack = 1,
AppFile,
McuFile
};
enum UpgradeProgress {
McuPermissionPrg = 2,
McuMsgNotifyPrg = 10,
McuDataSendPrg = 90,
McuUpgradeResultPrg = 92,
AppUpgradePrg = 94,
FileSystemUpgradePrg = 98,
UpgradeFinishPrg = 100
};
enum OtaStatus {
Ready = 0,
Downloading,
DownloadFailed,
Md5CheckFailed,
DownloadFinish,
Uncompressing,
UncompressFailed,
Updating,
ForbidUpdate,
UpdateFailed,
UpdateFailedByLen,
UpdateFailedByCheck,
UpdateJsonNotExist,
UpdateFinish,
UpdateFinishRestart
};
Q_ENUM(OtaStatus)
explicit OtaUpdate(QObject *parent = nullptr);
static OtaUpdate* getInstance();
bool upgrade() const;
void setUpgrade(bool upgrade);
int downloadPrg() const;
void setDownloadPrg(int);
int upgradePrg() const;
void setUpgradePrg(int);
int status() const;
void setStatus(int);
QString speed() const;
void setSpeed(QString);
QString fileSize() const;
void setFileSize(QString);
QString remainTime() const;
void setRemainTime(QString);
bool speedDisp() const;
Q_INVOKABLE void startUpgrade();
Q_INVOKABLE int getUpgradeVersion();
signals:
void upgradeChanged(bool);
void downloadPrgChanged(int);
void statusChanged(int);
void speedChanged(QString);
void fileSizeChanged(QString);
void remainTimeChanged(QString);
void upgradePrgChanged(int);
public slots:
// t-smart下发升级信息
void downloadUpgradePack(QVariantMap vMap);
// 下载文件
void writeFile();
// 下载文件结束
void downloadFinished();
void upgradeFlag(QVariantMap &vMap);
void updatingFlag(QVariantMap &vMap);
void upgradeResultFlag(QVariantMap &vMap);
void otaPermissionFlag(QVariantMap &vMap);
private:
QString getFileName(int type);
void startDownload();
void startUpdate();
void uncompressUpgradeFile();
void rebootOpe();
bool parseUpgradeJson();
void startMcuUpgrade();
void startAppUpgrade();
void startFileSystemUpgrade();
private:
bool m_isHasUpgrade{false}; // 当前是否有升级
int m_downloadPrg{0}; // 下载进度
int m_upgradePrg{0}; // 升级进度
int m_status{Ready}; // 升级状态
QString m_speed; // 下载速度
QString m_fileSizeStr; // 下载文件显示字符串
QString m_remainTime; // 剩余时间
quint64 m_fileSize{0}; // 已接收文件大小
QList<QVariantMap> m_otaList; // 待升级列表
QCryptographicHash m_cryHash{QCryptographicHash::Md5};
#ifdef IS_REPORT_SPEED
QElapsedTimer m_elapsedTimer; // 用于计算下载速度、剩余时间
#endif
quint64 m_totalFileSize{0}; // 升级文件总大小
QVariantMap m_currOtaMap; // 当前升级版本信息
QByteArray m_mcuByte; // mcu文件大小
QTimer m_timeoutTimer; // 下载/升级过程中超时定时器
};
#endif // OTAUPDATE_H
#include "otaupdate.h"
#include "t_smartwrapper.h"
#include "globalsetting.h"
#include "cloudcomm.h"
#include <QNetworkReply>
#include <QFile>
#include <QDir>
#include <QJsonDocument>
#include <QJsonObject>
#include <QtMath>
#include "serialportdataparse.h"
#include "crc32.h"
enum UpdateType {
NoType = 0,
McuType = 0x01,
AppType = 0x02,
FileSystemType = 0x04
};
#define MCU_VERIFY_HEAD_LEN 64 // MCU升级头文件封装长度
#define MCU_UPGRADE_FRAME_DATE_LEN 512 // MCU升级帧数据长度
static int updateType = NoType; // 是否重启系统
OtaUpdate::OtaUpdate(QObject *parent)
: QObject{parent}
{
m_otaList.clear();
m_timeoutTimer.setInterval(3*60*1000);
connect(&m_timeoutTimer, &QTimer::timeout, this, [this](){
if (m_status == Downloading) {
setStatus(DownloadFailed);
} else if (m_status == Updating) {
setStatus(UpdateFailed);
}
m_timeoutTimer.stop();
});
// 下载进度变化重启超时定时器
connect(this, &OtaUpdate::downloadPrgChanged, this, [this](){
m_timeoutTimer.start();
});
// 升级进度变化重启超时定时器
connect(this, &OtaUpdate::upgradePrgChanged, this, [this](){
m_timeoutTimer.start();
});
connect(T_SmartWrapper::getInstance(), &T_SmartWrapper::otaUpgradeUrl, this, &OtaUpdate::downloadUpgradePack);
connect(SerialPortDataParse::getInstance(), &SerialPortDataParse::sig_parseCmd0xA3Frame, this, &OtaUpdate::upgradeFlag);
connect(SerialPortDataParse::getInstance(), &SerialPortDataParse::sig_parseCmd0xA4Frame, this, &OtaUpdate::updatingFlag);
connect(SerialPortDataParse::getInstance(), &SerialPortDataParse::sig_parseCmd0xA5Frame, this, &OtaUpdate::upgradeResultFlag);
connect(SerialPortDataParse::getInstance(), &SerialPortDataParse::sig_parseCmd0xA0Frame, this, &OtaUpdate::otaPermissionFlag);
}
OtaUpdate *OtaUpdate::getInstance()
{
static OtaUpdate instance;
return &instance;
}
bool OtaUpdate::upgrade() const
{
return m_isHasUpgrade;
}
void OtaUpdate::setUpgrade(bool upgrade)
{
m_isHasUpgrade = upgrade;
emit upgradeChanged(upgrade);
}
int OtaUpdate::downloadPrg() const
{
return m_downloadPrg;
}
void OtaUpdate::setDownloadPrg(int progress)
{
m_downloadPrg = progress;
emit downloadPrgChanged(progress);
}
int OtaUpdate::upgradePrg() const
{
return m_upgradePrg;
}
void OtaUpdate::setUpgradePrg(int upgradePrg)
{
m_upgradePrg = upgradePrg;
emit upgradePrgChanged(upgradePrg);
}
int OtaUpdate::status() const
{
return m_status;
}
void OtaUpdate::setStatus(int status)
{
if (m_status != status) {
m_status = status;
emit statusChanged(status);
}
}
QString OtaUpdate::speed() const
{
return m_speed;
}
void OtaUpdate::setSpeed(QString speed)
{
m_speed = speed;
emit speedChanged(speed);
}
QString OtaUpdate::fileSize() const
{
return m_fileSizeStr;
}
void OtaUpdate::setFileSize(QString fileSize)
{
m_fileSizeStr = fileSize;
emit fileSizeChanged(fileSize);
}
QString OtaUpdate::remainTime() const
{
return m_remainTime;
}
void OtaUpdate::setRemainTime(QString remainTime)
{
m_remainTime = remainTime;
emit remainTimeChanged(remainTime);
}
bool OtaUpdate::speedDisp() const
{
#ifdef IS_REPORT_SPEED
return true;
#else
return false;
#endif
}
void OtaUpdate::startUpgrade()
{
m_fileSize = 0;
m_totalFileSize = 0;
m_cryHash.reset();
setDownloadPrg(0);
setUpgradePrg(0);
updateType = NoType;
foreach (QVariantMap vMap, m_otaList) {
m_totalFileSize += vMap.value("fw_sz").toInt();
}
// 移除已存在的升级包
int type = m_otaList.first().value("fw_type").toInt();
QString fileName = getFileName(type);
QFile file(fileName);
if (file.exists()) {
file.remove();
}
startDownload();
#ifdef IS_REPORT_SPEED
m_elapsedTimer.start();
#endif
}
int OtaUpdate::getUpgradeVersion()
{
if (m_otaList.isEmpty()) {
return m_currOtaMap.value("target_ver").toInt();
} else {
return m_otaList.first().value("target_ver").toInt();
}
}
void OtaUpdate::downloadUpgradePack(QVariantMap vMap)
{
if (m_otaList.isEmpty()) {
m_otaList.append(vMap);
setUpgrade(true);
}
}
QString OtaUpdate::getFileName(int type) {
switch (type) {
case FullPack:
return "upgrade.tar";
case AppFile:
return "app.tar";
case McuFile:
return "mcu.tar";
default:
return "unknown.tar";
}
}
void OtaUpdate::startDownload()
{
m_currOtaMap = m_otaList.first();
QString url = m_otaList.first().value("uri").toString();
CloudComm::getInstance()->downloadUpgradePack(url);
// 开启超时定时器
m_timeoutTimer.start();
}
void OtaUpdate::startUpdate()
{
setStatus(Updating);
QDir currDir = QDir::current();
currDir.cd("ota");
QStringList mcuList = currDir.entryList(QStringList("*.bin"), QDir::Files);
if (!mcuList.isEmpty()) { // mcu文件存在
QString fileName = QString(currDir.absolutePath()+"/%1").arg(mcuList.at(0));
QFile mcuFile(fileName); // Mcu执行文件
if (mcuFile.exists()) {
if (mcuFile.open(QIODevice::ReadOnly)) {
updateType |= McuType;
m_mcuByte = mcuFile.readAll();
mcuFile.close();
uint16_t data = 0x1003;
SerialPortDataParse::getInstance()->otaPermissionSet(data);
} else {
setStatus(UpdateFailed);
qDebug()<<"Mcu file open failed by: "<<mcuFile.errorString();
}
}
} else { // 开始app升级
startAppUpgrade();
}
}
void OtaUpdate::startAppUpgrade()
{
// 升级主控
QDir currDir = QDir::current();
QFile uiFile(currDir.absolutePath()+"/ota/WallManage"); // UI执行文件
if (uiFile.exists()) {
updateType |= AppType;
QString mv = QString("mv %1 %2").arg(uiFile.fileName()).arg(currDir.absolutePath());
system(mv.toUtf8().data());
}
QFile commFile(currDir.absolutePath()+"/ota/NetProcess"); // Comm执行文件
if (commFile.exists()) {
updateType |= AppType;
QString mv = QString("mv %1 %2").arg(commFile.fileName()).arg(currDir.absolutePath());
system(mv.toUtf8().data());
}
if (updateType & AppType) {
// 解析app应用版本
bool ret = parseUpgradeJson();
if (!ret) {
return;
}
}
setUpgradePrg(AppUpgradePrg);
startFileSystemUpgrade();
// 设置升级成功标志,当前应用版本,更新到云端
#ifdef PLATFORM_ARM
#ifdef IS_SET_VERSION
int ver = m_currOtaMap.value("target_ver").toInt();
GlobalSetting::getInstance()->setVersion(ver);
#endif
#endif
setUpgradePrg(UpgradeFinishPrg);
// 升级主控
if ((updateType & AppType) || (updateType & FileSystemType)) {
setStatus(UpdateFinishRestart);
system("sync"); // 将缓冲文件写到硬盘中
} else { // 升级mcu
setStatus(UpdateFinish);
}
QTimer::singleShot(3000, this, &OtaUpdate::rebootOpe);
}
void OtaUpdate::startFileSystemUpgrade()
{
QDir currDir = QDir::current();
QFile fileSystemFile(currDir.absolutePath()+"/ota/update.img");
if (fileSystemFile.exists()) {
updateType |= FileSystemType;
// QString mv = QString("mv %1 %2").arg(fileSystemFile.fileName()).arg(currDir.absolutePath());
// system(mv.toUtf8().data());
}
setUpgradePrg(FileSystemUpgradePrg);
}
void OtaUpdate::uncompressUpgradeFile()
{
setStatus(Uncompressing);
QDir home = QDir::current();
const QString otaDir = "ota";
// 创建ota目录
if (!home.exists(otaDir)) {
if(!home.mkdir(otaDir)) {
setStatus(UncompressFailed);
return;
}
}
QString otaName = getFileName(FullPack);
QFile file(otaName);
// 进入ota目录
if (!home.cd(otaDir)) {
setStatus(UncompressFailed);
return;
} else {
// 移除ota内所有文件
QStringList fileList = home.entryList(QDir::Files|QDir::NoDotAndDotDot);
foreach(QString fileName, fileList) {
home.remove(fileName);
}
if (home.isEmpty(QDir::Files | QDir::NoDotAndDotDot)) {
// 将upgrade.tar移动到ota目录
otaName = home.path()+"/"+otaName;
if (!file.rename(otaName)) {
setStatus(UncompressFailed);
return;
}
}
}
// 解压
QString str = QString("tar -xf %1 -C %2").arg(otaName).arg(home.absolutePath());
system(str.toUtf8().data());
// 存在升级文件,进入升级流程
if (home.isEmpty(QDir::Files | QDir::NoDotAndDotDot)) {
setStatus(UncompressFailed);
} else {
startUpdate();
}
}
void OtaUpdate::startMcuUpgrade()
{
if (m_mcuByte.size() >= MCU_VERIFY_HEAD_LEN) {
QVariantMap vMap;
vMap.insert("location", 0x01);
// 标识符
uint identify = (m_mcuByte[0] << 24) + (m_mcuByte[1] << 16) + (m_mcuByte[2] << 8) + m_mcuByte[3];
vMap.insert("identify", identify);
// md5
QByteArray md5Bin = m_mcuByte.mid(4, 32);
vMap.insert("md5", md5Bin);
// crc32
uint32_t crc32Bin = (m_mcuByte[36] << 24) + (m_mcuByte[37] << 16) + (m_mcuByte[38] << 8) + m_mcuByte[39];
vMap.insert("crc32", crc32Bin);
// 代码文件大小
uint fileSize = (m_mcuByte[40] << 24) + (m_mcuByte[41] << 16) + (m_mcuByte[42] << 8) + m_mcuByte[43];
vMap.insert("fileSize", fileSize);
// 软件版本号
uint32_t mcuVer = (m_mcuByte[44] << 24) + (m_mcuByte[45] << 16) + (m_mcuByte[46] << 8) + m_mcuByte[47];
vMap.insert("version", mcuVer);
// 通知mcu升级
SerialPortDataParse::getInstance()->startSlaveUpgrade(vMap);
} else {
setStatus(UpdateFailed);
QString err = QString("Mcu file size less than %1, size: %2").arg(MCU_VERIFY_HEAD_LEN).arg(m_mcuByte.size());
qDebug()<<err;
}
}
void OtaUpdate::rebootOpe()
{
#ifdef PLATFORM_ARM
if (updateType & FileSystemType) {
QDir currDir = QDir::current();
QString fileSystemPath = currDir.absolutePath()+"/ota/update.img";
QString cmd = QString("update ota %1").arg(fileSystemPath);
system(cmd.toUtf8().data());
} else if (updateType & AppType) {
system("reboot");
} else if (updateType & McuType) {
setUpgrade(false);
setStatus(Ready);
}
#endif
if (!m_otaList.isEmpty()) {
m_otaList.removeFirst();
}
}
bool OtaUpdate::parseUpgradeJson()
{
bool ret = false;
QString json = QDir::currentPath()+"/ota/upgrade.json";
QFile file(json);
if (file.exists()) {
if (file.open(QIODevice::ReadOnly)) {
QByteArray data = file.readAll();
QJsonDocument doc = QJsonDocument::fromJson(data);
if (doc.isObject()) {
QJsonObject obj = doc.object();
int app_version = obj.value("app_version").toInt();
GlobalSetting::getInstance()->setAppVer(app_version);
ret = true;
}
} else {
setStatus(UpdateFailed);
}
} else {
setStatus(UpdateJsonNotExist);
}
return ret;
}
void OtaUpdate::upgradeFlag(QVariantMap &vMap)
{
uint8_t flag = vMap.value("flag").toUInt();
if (flag == 0) {
setStatus(ForbidUpdate);
} else { // 开始升级
QByteArray sendData = m_mcuByte.mid(MCU_VERIFY_HEAD_LEN, MCU_UPGRADE_FRAME_DATE_LEN);
SerialPortDataParse::getInstance()->sendUpgradeData(1, sendData);
}
setUpgradePrg(McuMsgNotifyPrg);
}
void OtaUpdate::updatingFlag(QVariantMap &vMap)
{
static int cnt = 0;
uint8_t flag = vMap.value("flag").toUInt();
uint16_t frameNo = vMap.value("frameNo").toUInt();
// 判断是否是最后一帧
uint16_t totalFrame = 0;
uint64_t fileSize = m_mcuByte.size()-MCU_VERIFY_HEAD_LEN;
if (fileSize % MCU_UPGRADE_FRAME_DATE_LEN == 0) {
totalFrame = fileSize / MCU_UPGRADE_FRAME_DATE_LEN;
} else {
totalFrame = qFloor(fileSize/MCU_UPGRADE_FRAME_DATE_LEN)+1;
}
QByteArray sendData;
if (flag == 0) {
if (cnt < 2) {
cnt++;
if (frameNo == totalFrame) { // 最后一帧
sendData = m_mcuByte.mid(MCU_VERIFY_HEAD_LEN+(frameNo-1)*MCU_UPGRADE_FRAME_DATE_LEN);
} else {
sendData = m_mcuByte.mid(MCU_VERIFY_HEAD_LEN+(frameNo-1)*MCU_UPGRADE_FRAME_DATE_LEN, MCU_UPGRADE_FRAME_DATE_LEN);
}
SerialPortDataParse::getInstance()->sendUpgradeData(frameNo, sendData);
} else {
setStatus(UpdateFailed);
}
} else {
cnt = 0;
if (frameNo == totalFrame) { // 最后一帧
// 请求升级结果
SerialPortDataParse::getInstance()->getUpgradeResult();
} else {
sendData = m_mcuByte.mid(MCU_VERIFY_HEAD_LEN+frameNo*MCU_UPGRADE_FRAME_DATE_LEN, MCU_UPGRADE_FRAME_DATE_LEN);
SerialPortDataParse::getInstance()->sendUpgradeData(frameNo+1, sendData);
}
}
int prg = McuMsgNotifyPrg+qRound((McuDataSendPrg-McuMsgNotifyPrg)*1.0/totalFrame*frameNo);
setUpgradePrg(prg);
}
void OtaUpdate::upgradeResultFlag(QVariantMap &vMap)
{
setUpgradePrg(McuUpgradeResultPrg);
uint8_t flag = vMap.value("flag").toUInt();
if (flag == 0) {
startAppUpgrade();
} else if (flag == 1) {
setStatus(UpdateFailedByLen);
} else if (flag == 2) {
setStatus(UpdateFailedByCheck);
} else {
setStatus(UpdateFailed);
}
}
void OtaUpdate::otaPermissionFlag(QVariantMap &vMap)
{
uint16_t permission = vMap.value("permission").toUInt();
if (permission == 0x5003) {
startMcuUpgrade();
} else {
setStatus(UpdateFailed);
QString err = QString("Mcu permission error By: 0x%1").arg(QString::number(permission, 16));
qDebug()<<err;
}
setUpgradePrg(McuPermissionPrg);
}
void OtaUpdate::writeFile()
{
QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
QString fileName;
foreach (QVariantMap vMap, m_otaList) {
if (vMap.value("uri").toString() == reply->url().toString()) {
fileName = getFileName(vMap.value("fw_type").toInt());
break;
}
}
if (reply->error() == QNetworkReply::NoError) {
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
QFile file(fileName);
if (file.open(QIODevice::WriteOnly|QIODevice::Append)) {
setStatus(Downloading);
QByteArray byte = reply->readAll();
file.write(byte);
m_cryHash.addData(byte);
quint64 size = byte.size();
m_fileSize += size;
// 设置升级进度
setDownloadPrg(qRound(m_fileSize*100.0/m_totalFileSize));
// 设置文件大小
QString total = QString::number(m_totalFileSize*1.0/(1024*1024), 'f', 2);
if (m_fileSize >= 1024*1024) {
QString MB = QString::number(m_fileSize*1.0/(1024*1024), 'f', 2);
setFileSize(QString("<font color='yellow'>%1 MB</font>/%2 MB").arg(MB).arg(total));
} else {
if (m_totalFileSize >= 1024*1024) {
setFileSize(QString("<font color='yellow'>%1 KB</font>/%2 MB").arg(qRound(m_fileSize*1.0/1024)).arg(total));
} else {
setFileSize(QString("<font color='yellow'>%1 KB</font>/%2 KB").arg(qRound(m_fileSize*1.0/1024)).arg(qRound(m_totalFileSize*1.0/1024)));
}
}
#ifdef IS_REPORT_SPEED
qint64 elapsed = m_elapsedTimer.elapsed();
if (elapsed > 0) {
// 设置下载速度 KB/s
qreal KB_s = size/elapsed*1000.0/1024;
if (KB_s >= 1024) {
QString MB_s = QString::number(KB_s/1024, 'f', 1);
setSpeed(QString("%1 MB/s").arg(MB_s));
} else {
QString KB_Str = QString::number(KB_s, 'f', 1);
setSpeed(QString("%1 KB/s").arg(KB_Str));
}
// 设置剩余时间
if (KB_s > 0) {
qreal remain = (m_totalFileSize-m_fileSize)/KB_s/1024;
if (remain >= 60) {
setRemainTime(QString("%1 Min").arg(qRound(remain/60)));
} else {
setRemainTime(QString("%1 s").arg(qRound(remain)));
}
}
}
#endif
} else {
setStatus(DownloadFailed);
}
} else {
setStatus(DownloadFailed);
}
} else {
setStatus(DownloadFailed);
}
#ifdef IS_REPORT_SPEED
m_elapsedTimer.start();
#endif
}
void OtaUpdate::downloadFinished()
{
QString fileName;
QString md5;
QNetworkReply *reply = static_cast<QNetworkReply*>(sender());
foreach (QVariantMap vMap, m_otaList) {
if (vMap.value("uri").toString() == reply->url().toString()) {
fileName = getFileName(vMap.value("fw_type").toInt());
md5 = vMap.value("md5").toString();
break;
}
}
QFile file(fileName);
if (file.isOpen()) {
file.close();
}
if (reply->error() == QNetworkReply::NoError) {
if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
qDebug()<<"_________md5__________"<<m_cryHash.result().toHex()<<md5.toLower();
if (m_cryHash.result().toHex() != md5.toLower()) {
setStatus(Md5CheckFailed);
} else {
setStatus(DownloadFinish);
QTimer::singleShot(1000, this, &OtaUpdate::uncompressUpgradeFile);
// m_otaList.removeFirst();
// if (!m_otaList.isEmpty()) {
// // 下载下一个升级文件
// startDownload();
// } else {
// setStatus(DownloadFinish);
// QTimer::singleShot(1000, this, &OtaUpdate::uncompressUpgradeFile);
// }
}
} else {
setStatus(DownloadFailed);
}
} else {
setStatus(DownloadFailed);
}
reply->deleteLater();
}