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

    }
}


Ymodem协议源码

源码链接

05-08 07:32