【嵌入式——QT】QT集成Ymodem协议使用UDP进行传输

发布于:2024-05-08 ⋅ 阅读:(36) ⋅ 点赞:(0)

Ymodem协议

帧的数据格式

帧头、包号、包号反码、数据、校验。

帧头 包号 包号反码 数据 校验高位 校验低位
Soh/Stx 0x00 0xFF DATA CRC_H CRC_L

帧头

以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)命令后,才可以开始发送起始帧。

起始帧

帧头 包号 包号反码 文件名称 文件大小 填充区 校验高位 校验低位
CodeSoh 0x00 0xFF FileName+0x00 FileSize+0x00 NULL(0x00) CRC_H CRC_L

文件名称后必须添加0x00作为结束,文件大小值后必须加0x00作为结束,余下的位置以0x00填充。

数据帧

帧头 包号 包号反码 有效数据 校验高位 校验低位
CodeSoh/CodeStx 0x00 0xFF DATA CRC_H CRC_L

对于SOH帧,若余下数据小于128字节,则以0x1A填充,该帧长度仍为133字节;
对于STX帧需考虑几种情况:

  • 余下数据等于1024字节,以1029长度帧发送;
  • 余下数据小于1024字节,但大于128字节,以1029字节帧长度发送,无效数据以0x1A填充;
  • 余下数据等于128字节,以133字节帧长度发送;
  • 余下数据小于128字节,以133字节帧长度发送,无效数据以0x1A填充;

结束帧

帧头 包号 包号反码 数据区 校验高位 校验低位
CodeSoh 0x00 0xFF DATA CRC_H CRC_L
数据区,校验都以0x00填充。

代码块

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由接收端发送;

命令 命令码 备注
CodeNone 0x00
CodeSoh 0x01 133字节长度
CodeStx 0x02 1024字节长度
CodeEot 0x04 文件传输结束指令
CodeAck 0x06 接收正确指令
CodeNak 0x15 重传当前数据包请求指令
CodeCan 0x18 取消传输指令,连续发送5个该命令,终止传输
CodeC 0x43 字符C
CodeA1 0x41
CodeA2 0x61

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"));

    }
}


Ymodem协议源码

源码链接