【嵌入式——QT】QT集成Ymodem协议使用UDP进行传输
Ymodem协议
帧的数据格式
帧头、包号、包号反码、数据、校验。
帧头
以Soh(0x01)开始的数据包,信息块是128字节,该帧类型总长度为133字节。
以Stx(0x02)开始的数据包,信息块是1024字节,该帧类型总长度为1029字节。
包号
包号是为数据块的编号,将要传送的数据进行分块编号,只有一个字节,范围为0~255。大于255的则归零重复计算。
校验
Ymodem采用的是CRC16校验算法,校验值为2字节。
uint16_t Ymodem::crc16(uint8_t *buff, uint32_t len)
{
uint16_t crc = 0;
while(len--)
{
crc ^= (uint16_t)(*(buff++)) << 8;
for(int i = 0; i < 8; i++)
{
if(crc & 0x8000)
{
crc = (crc << 1) ^ 0x1021;
}
else
{
crc = crc << 1;
}
}
}
return crc;
}
通讯过程
握手信号
发送方收到接收方发送的CodeC(0x43)命令后,才可以开始发送起始帧。
起始帧
文件名称后必须添加0x00作为结束,文件大小值后必须加0x00作为结束,余下的位置以0x00填充。
数据帧
对于SOH帧,若余下数据小于128字节,则以0x1A填充,该帧长度仍为133字节;
对于STX帧需考虑几种情况:
- 余下数据等于1024字节,以1029长度帧发送;
- 余下数据小于1024字节,但大于128字节,以1029字节帧长度发送,无效数据以0x1A填充;
- 余下数据等于128字节,以133字节帧长度发送;
- 余下数据小于128字节,以133字节帧长度发送,无效数据以0x1A填充;
结束帧
代码块
void Ymodem::transmit()
{
switch(stage)
{
case StageNone:
{
transmitStageNone();
break;
}
case StageEstablishing:
{
transmitStageEstablishing();
break;
}
case StageEstablished:
{
transmitStageEstablished();
break;
}
case StageTransmitting:
{
transmitStageTransmitting();
break;
}
case StageFinishing:
{
transmitStageFinishing();
break;
}
default:
{
transmitStageFinished();
}
}
}
Ymodem命令
CodeEot、CodeCan由发送端发送;
CodeAck、CodeNak、CodeC由接收端发送;
QT实现
YmodemFileTransmit.h
#ifndef YMODEMFILETRANSMIT_H
#define YMODEMFILETRANSMIT_H
#include <QFile>
#include <QTimer>
#include <QObject>
#include "Ymodem.h"
#include <QUdpSocket>
class YmodemFileTransmit : public QObject, public Ymodem
{
Q_OBJECT
public:
explicit YmodemFileTransmit(QObject* parent = nullptr);
~YmodemFileTransmit();
void setFileName(const QString& name);
void setIpAddress(const QString& ip);
void setPortNumber(quint16 port);
bool startTransmit();
void stopTransmit();
int getTransmitProgress();
Status getTransmitStatus();
signals:
void transmitProgress(int progress);
void transmitStatus(YmodemFileTransmit::Status status);
public slots:
void readTimeOut();
void writeTimeOut();
private:
Code callback(Status status, uint8_t* buff, uint32_t* len);
uint32_t read(uint8_t* buff, uint32_t len);
uint32_t write(uint8_t* buff, uint32_t len);
QFile* file;
QTimer* readTimer;
QTimer* writeTimer;
QUdpSocket* udpClient;
int progress;
Status status;
uint64_t fileSize;
uint64_t fileCount;
QString serverIp;
uint16_t serverPort;
};
#endif // YMODEMFILETRANSMIT_H
YmodemFileTransmit.cpp
#include "YmodemFileTransmit.h"
#include <QFileInfo>
#include <QNetworkDatagram>
#include <QThread>
#define READ_TIME_OUT (10)
#define WRITE_TIME_OUT (1000)
YmodemFileTransmit::YmodemFileTransmit(QObject* parent) :
QObject(parent),
file(new QFile),
readTimer(new QTimer),
writeTimer(new QTimer),
udpClient(new QUdpSocket)
{
setTimeDivide(499);
setTimeMax(5);
setErrorMax(999);
connect(readTimer, SIGNAL(timeout()), this, SLOT(readTimeOut()));
connect(writeTimer, SIGNAL(timeout()), this, SLOT(writeTimeOut()));
}
YmodemFileTransmit::~YmodemFileTransmit()
{
delete file;
delete readTimer;
delete writeTimer;
delete udpClient;
}
void YmodemFileTransmit::setFileName(const QString& name)
{
file->setFileName(name);
}
void YmodemFileTransmit::setIpAddress(const QString& ip)
{
serverIp = ip;
}
void YmodemFileTransmit::setPortNumber(quint16 port)
{
serverPort = port;
}
bool YmodemFileTransmit::startTransmit()
{
progress = 0;
status = StatusEstablish;
QByteArray array;
array.append(0x02);
array.append(0x01);
array.append(0xFF);
array.append(0x07);
array.append(0x01);
array.append(0x09);
array.append(0x03);
QHostAddress targetAddr(serverIp);
int ret = udpClient->writeDatagram(array, targetAddr, serverPort);
if(ret > 0) {
QThread::msleep(50);
readTimer->start(READ_TIME_OUT);
return true;
} else {
return false;
}
}
void YmodemFileTransmit::stopTransmit()
{
file->close();
abort();
status = StatusAbort;
writeTimer->start(WRITE_TIME_OUT);
}
int YmodemFileTransmit::getTransmitProgress()
{
return progress;
}
Ymodem::Status YmodemFileTransmit::getTransmitStatus()
{
return status;
}
void YmodemFileTransmit::readTimeOut()
{
readTimer->stop();
transmit();
if((status == StatusEstablish) || (status == StatusTransmit)) {
readTimer->start(READ_TIME_OUT);
}
}
void YmodemFileTransmit::writeTimeOut()
{
writeTimer->stop();
transmitStatus(status);
}
Ymodem::Code YmodemFileTransmit::callback(Status status, uint8_t* buff, uint32_t* len)
{
switch(status) {
case StatusEstablish:
if(file->open(QFile::ReadOnly) == true) {
QFileInfo fileInfo(*file);
fileSize = fileInfo.size();
fileCount = 0;
strcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());
strcpy((char*)buff + fileInfo.fileName().toLocal8Bit().size() + 1, QByteArray::number(fileInfo.size()).data());
*len = YMODEM_PACKET_SIZE;
YmodemFileTransmit::status = StatusEstablish;
transmitStatus(StatusEstablish);
return CodeAck;
} else {
YmodemFileTransmit::status = StatusError;
writeTimer->start(WRITE_TIME_OUT);
return CodeCan;
}
case StatusTransmit:
if(fileSize != fileCount) {
if((fileSize - fileCount) > YMODEM_PACKET_SIZE) {
fileCount += file->read((char*)buff, YMODEM_PACKET_1K_SIZE);
*len = YMODEM_PACKET_1K_SIZE;
} else {
fileCount += file->read((char*)buff, YMODEM_PACKET_SIZE);
*len = YMODEM_PACKET_SIZE;
}
progress = (int)(fileCount * 100 / fileSize);
YmodemFileTransmit::status = StatusTransmit;
transmitProgress(progress);
transmitStatus(StatusTransmit);
return CodeAck;
} else {
YmodemFileTransmit::status = StatusTransmit;
transmitStatus(StatusTransmit);
return CodeEot;
}
case StatusFinish:
file->close();
YmodemFileTransmit::status = StatusFinish;
writeTimer->start(WRITE_TIME_OUT);
return CodeAck;
case StatusAbort:
file->close();
YmodemFileTransmit::status = StatusAbort;
writeTimer->start(WRITE_TIME_OUT);
return CodeCan;
case StatusTimeout:
YmodemFileTransmit::status = StatusTimeout;
writeTimer->start(WRITE_TIME_OUT);
return CodeCan;
default:
file->close();
YmodemFileTransmit::status = StatusError;
writeTimer->start(WRITE_TIME_OUT);
return CodeCan;
}
}
uint32_t YmodemFileTransmit::read(uint8_t* buff, uint32_t len)
{
QNetworkDatagram datagram =udpClient->receiveDatagram(len);
QByteArray array = datagram.data();
uint32_t lenArray = array.size();
uint32_t lenBuff = len;
uint32_t length = qMin(lenArray, lenBuff);
memcpy(buff, array, length);
return length;
}
uint32_t YmodemFileTransmit::write(uint8_t* buff, uint32_t len)
{
QHostAddress targetAddr(serverIp);
int ret = udpClient->writeDatagram((char*)buff, len, targetAddr, serverPort);
return ret;
}
BootLoader.h
#ifndef BOOTLOADER_H
#define BOOTLOADER_H
#include <QWidget>
#include <QUdpSocket>
#include <YmodemFileTransmit.h>
QT_BEGIN_NAMESPACE
namespace Ui
{
class BootLoader;
}
QT_END_NAMESPACE
class BootLoader : public QWidget
{
Q_OBJECT
public:
BootLoader(QWidget* parent = nullptr);
~BootLoader();
YmodemFileTransmit* ymodemFileTransmit;
public slots:
void on_pushButtonConnect_clicked();
void on_pushButtonBrowse_clicked();
void on_pushButtonSend_clicked();
void readData();
void transmitProgress(int progress);
void transmitStatus(YmodemFileTransmit::Status status);
private:
Ui::BootLoader* ui;
bool firemwareTransmitStatus;
QUdpSocket* udpClient;
};
#endif // BOOTLOADER_H
BootLoader.cpp
#include "BootLoader.h"
#include "ui_BootLoader.h"
#include <QByteArray>
#include <QDebug>
#include <QNetworkDatagram>
#include <QFileDialog>
#include <QMessageBox>
#define SERVER_ADDR "192.168.xxx.xxx"
#define SERVER_PORT 4002
BootLoader::BootLoader(QWidget* parent)
: QWidget(parent)
, ui(new Ui::BootLoader)
{
ui->setupUi(this);
this->setWindowTitle(tr("EthernetYmodem"));
this->setWindowIcon(QIcon(":/images/main.ico"));
ymodemFileTransmit = new YmodemFileTransmit();
connect(ymodemFileTransmit, SIGNAL(transmitProgress(int)), this, SLOT(transmitProgress(int)));
connect(ymodemFileTransmit, SIGNAL(transmitStatus(YmodemFileTransmit::Status)), this, SLOT(transmitStatus(YmodemFileTransmit::Status)));
udpClient = new QUdpSocket(this);
connect(udpClient, &QUdpSocket::readyRead, this, &BootLoader::readData);
firemwareTransmitStatus = false;
ui->pushButtonSend->setEnabled(false);
ui->pushButtonConnect->setEnabled(true);
}
BootLoader::~BootLoader()
{
delete ui;
}
void BootLoader::on_pushButtonConnect_clicked()
{
QByteArray array;
array.append(0x02);
array.append(0x01);
array.append(0xFF);
array.append(0x07);
array.append(0x01);
array.append(0x09);
array.append(0x03);
QHostAddress targetAddr(SERVER_ADDR);
int ret = udpClient->writeDatagram(array, targetAddr, SERVER_PORT);
qDebug()<<"ret"<<ret;
}
void BootLoader::on_pushButtonBrowse_clicked()
{
QString curPath = QDir::currentPath();
ui->lineEditFilePath->setText(QFileDialog::getOpenFileName(this, u8"打开文件", curPath, u8"任意文件 (*.*)"));
}
void BootLoader::on_pushButtonSend_clicked()
{
udpClient->abort();
if(ui->lineEditFilePath->text().isEmpty()) {
QMessageBox::warning(this, tr("!!!"), tr("Please select a file!"));
return;
}
if(firemwareTransmitStatus == false) {
ymodemFileTransmit->setFileName(ui->lineEditFilePath->text());
ymodemFileTransmit->setIpAddress(SERVER_ADDR);
ymodemFileTransmit->setPortNumber(SERVER_PORT);
if(ymodemFileTransmit->startTransmit() == true) {
firemwareTransmitStatus = true;
ui->progressBar->setValue(0);
ui->pushButtonSend->setText(tr("Cancel"));
ui->pushButtonConnect->setEnabled(false);
} else {
QMessageBox::warning(this, tr("Failure"), tr("File failed to send!"), tr("Closed"));
ui->pushButtonSend->setText(tr("Send"));
ui->pushButtonConnect->setEnabled(true);
}
} else {
ymodemFileTransmit->stopTransmit();
ui->pushButtonSend->setText(tr("Send"));
ui->pushButtonConnect->setEnabled(true);
}
}
void BootLoader::readData()
{
while(udpClient->hasPendingDatagrams()) {
QNetworkDatagram datagram = udpClient->receiveDatagram();
QByteArray receivedData = datagram.data();
qDebug() << "Received data:" << receivedData;
if(receivedData.size() > 0 && receivedData[0] == (char)0x43) {
ui->pushButtonSend->setEnabled(true);
ui->pushButtonConnect->setEnabled(false);
}
}
}
void BootLoader::transmitProgress(int progress)
{
ui->progressBar->setValue(progress);
}
void BootLoader::transmitStatus(Ymodem::Status status)
{
switch(status) {
case YmodemFileTransmit::StatusEstablish:
break;
case YmodemFileTransmit::StatusTransmit:
break;
case YmodemFileTransmit::StatusFinish:
firemwareTransmitStatus = false;
QMessageBox::information(this, tr("OK"), tr("Upgrade successed!"), QMessageBox::Yes);
ui->pushButtonSend->setText(tr("Send"));
ui->pushButtonSend->setEnabled(false);
ui->pushButtonConnect->setEnabled(true);
break;
case YmodemFileTransmit::StatusAbort:
firemwareTransmitStatus = false;
QMessageBox::warning(this, tr("failure"), tr("File failed to send!"), tr("Closed"));
break;
case YmodemFileTransmit::StatusTimeout:
firemwareTransmitStatus = false;
QMessageBox::warning(this, tr("failure"), tr("File failed to send!"), tr("Closed"));
break;
default:
firemwareTransmitStatus = false;
QMessageBox::warning(this, tr("failure"), tr("File failed to send!"), tr("Closed"));
}
}