1 概述
XMODEM协议是一种使用拨号调制解调器的个人计算机通信中广泛使用的异步文件运输协议。这种协议以128字节块的形式传输数据,并且每个块都使用一个校验和过程来进行错误检测。使用循环冗余校验的与XMODEM相应的一种协议称为XMODEM-CRC。还有一种是XMODEM-1K,它以1024字节一块来传输数据。YMODEM也是一种XMODEM的实现。它包括XMODEM-1K的所有特征,另外在一次单一会话期间为发送一组文件,增加了批处理文件传输模式。
本文利用C++实现XYModem-1K协议,并利用Qt串口类QSerialPort实现数据读写。
3 实现
3.4 XYModemSendFile
该模块实现XYModem协议发送文件。
流程图:
3.4.1 XYModemSendFile定义
class QSerialPort;
class XYModemSendFile : public QObject, public YModem
{
Q_OBJECT
public:
explicit XYModemSendFile(QSerialPort *serial, QObject *parent = nullptr);
public slots:
void startYModem(QString const& fileName);
void startXModem(QString const& fileName);
void stop() { { doSignal(); }}
void cancel() { tx_cancle(); }
signals:
void gotFileSize(quint64 filesize);
void progressInfo(quint32 blockNumber, quint64 bytesOfSend);
void error(QString const& e);
void finished();
protected:
uint32_t write(uint8_t const *data, uint32_t size) override;
uint32_t read(uint8_t *data, uint32_t size) override;
uint8_t get_code(bool isWait = true) override;
private:
bool singled() { return signal_; }
void doSignal() { signal_ = true; };
void doError(QString const& text);
private:
QSerialPort* serial_;
volatile bool signal_;
};
公共接口:
- startYModem 开始YModem协议发送文件
- startXModem 开始XModem协议发送文件
- stop 停止发送
- cancel 取消发送
信号:
- gotFileSize 文件大小信号
- progressInfo 传输进度信号
- error 出错信号
- finished 传输结束信号
重载接口:
- write 向串口写数据
- read 从串口读取数据
- get_code 读取操作码
3.4.2 XYModemSendFile实现
3.4.2.1 startYModem
void XYModemSendFile::startYModem(QString const& fileName)
{
QFileInfo fileInfo(fileName);
uint64_t fileSize = fileInfo.size();
std::string filename = fileInfo.fileName().toStdString();
std::string filesize = QString::number(fileSize).toStdString();
if(filename.size() + filesize.size() + 2 > SIZE1)
{
doError("Filename too long!");
return;
}
//Get Start flag
if(get_code() != C)
{
doError("Can not get C!");
return;
}
//SOH first block
emit gotFileSize(fileSize);
if(!tx_start(filename, filesize))
{
doError("Can not get ACK and C!");
return;
}
//Send data of file
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly))
{
doError(QString("%1 cannot be opened!").arg(fileName));
return;
}
uint8_t data[SIZE2];
quint64 bytesOfSend = 0;
quint32 block = 0;
bool is_end = false;
while(!singled())
{
quint64 size = file.read((char *)data, sizeof(data));
if(size == 0)
{
is_end = true;
break;
}
if(tx_send(data, size))
{
block++;
bytesOfSend += size;
emit progressInfo(block, bytesOfSend);
}
else
{
emit error("send is error");
break;
}
}
//Send end
if(is_end)
{
if(tx_eot())
{
if(tx_end())
{
//std::cout << "end is ok" << std::endl;
}
}
}
emit finished();
serial_->moveToThread(QApplication::instance()->thread());
}
函数流程:
- 获取文件名和文件大小字符串
- 如果文件名和文件大小字符串大小大于Size1(128)则返回文件名称太长错误返回
- 获取操作码
- 获得非C操作码,失败返回
- 发送文件大小信号gotFileSize
- 发送开始包(包括文件名称和文件大小。YModem协议发送该包,XModem协议不发送该包)
- 打开文件,如果失败则返回
- 读取文件内容
- 发送文件内容
- 发送成功发送,发送进度信号progressInfo
- 发送失败,则发送错误信号并返回
- 重复上述4步,直到文件发送完毕或停止
- 如果文件发送完毕包(tx_eot),发送文件结束标志
- 发送通信结束包(tx_end),结束通信。(这里只发送一个文件所以发送tx_end, 如果是多个文件需要发送tx_start开始新文件发送)
- 发送传输结束信号finished
3.4.2.2 startXModem
void XYModemSendFile::startXModem(QString const& fileName)
{
QFileInfo fileInfo(fileName);
uint64_t fileSize = fileInfo.size();
//Get Start flag
if(get_code() != C)
{
doError("Can not get C!");
return;
}
//SOH first block
emit gotFileSize(fileSize);
//Send data of file
QFile file(fileName);
if(!file.open(QIODevice::ReadOnly))
{
doError(QString("%1 cannot be opened!").arg(fileName));
return;
}
uint8_t data[SIZE2];
quint64 bytesOfSend = 0;
quint32 block = 0;
bool is_end = false;
next_id();
while(!singled())
{
quint64 size = file.read((char *)data, sizeof(data));
if(size == 0)
{
is_end = true;
break;
}
if(tx_send(data, size))
{
block++;
bytesOfSend += size;
emit progressInfo(block, bytesOfSend);
}
else
{
emit error("send is error");
break;
}
}
//Send end
if(is_end)
{
if(tx_eot())
{
//std::cout << "end is ok" << std::endl;
}
}
emit finished();
serial_->moveToThread(QApplication::instance()->thread());
}
函数流程:
- 获取文件大小
- 获取操作码
- 如果不是C码,失败返回
- 发送文件大小信号gotFileSize
- 打开文件,如果失败则返回
- 发送文件内容
- 发送成功发送,发送进度信号progressInfo
- 发送失败,则发送错误信号并返回
- 重复上述4步,直到文件发送完毕或停止
- 如果文件发送完毕包(tx_eot),发送文件结束标志
- 发送传输结束信号finished
3.4.2.3 write/read
uint32_t XYModemSendFile::write(uint8_t const *data, uint32_t size)
{
return serial_->write((const char *)data, size);
}
uint32_t XYModemSendFile::read(uint8_t *data, uint32_t size)
{
return serial_->read((char *)data, size);
}
函数说明:
- 直接调用串口读写接口实现读写接口。
3.4.2.4 get_code
uint8_t XYModemSendFile::get_code(bool isWait)
{
while(!singled())
{
if(serial_->waitForReadyRead(10))
{
uint8_t data[1] = { 0 };
read(data, sizeof(data));
return data[0];
}
if(!isWait)
break;
}
return MAX;
}
函数流程:
- 等待串口输入
- 如果有输入则读取一字节做为code码并返回.
- 如果一直没有输入直到调用stop后返回无效码MAX.
3.4.2.5 doError
void XYModemSendFile::doError(QString const& text)
{
emit error(text);
serial_->moveToThread(QApplication::instance()->thread());
emit finished();
}
函数流程:
- 发送错误信号
- 发送结束信号
Qt实现XYModem协议(四) Qt实现XYModem协议(六)