企業(yè)寬帶可以做網站嗎安卓優(yōu)化大師手機版下載
【QT】基于UDP/TCP/串口的Ymodom通訊協(xié)議客戶端
- 前言
- Ymodom實現(xiàn)
- QT實現(xiàn)
- 開源庫的二次開發(fā)-1
- 開源庫的二次開發(fā)-2
- 串口方式實現(xiàn)
- TCP方式實現(xiàn)
- UDP方式實現(xiàn)
- 補充:文件讀取
- 補充:QT 封裝成EXE
前言
Qt 運行環(huán)境 Desktop_Qt_5_11_2_MSVC2015_64bit ,基于Ymodom通訊協(xié)議,開發(fā)客戶端實現(xiàn)與設備的UDP /TCP /串口通訊。在前期測試過程中,主要用了網絡調試助手、串口調試助手、Virtual Serial Port Driver虛擬串口。
對Ymodom的了解過程中,主要學習了博文Ymodem協(xié)議詳解 、【嵌入式——QT】QT集成Ymodem協(xié)議使用UDP進行傳輸、qt隨手記——ymodem協(xié)議使用,里面對協(xié)議的規(guī)則進行了詳細的講述,方便理解Ymodom 是什么。
在沒有設備的情況下,用虛擬設備進行測試,發(fā)一個文件對應的指令如下:
43 ( C)
--
06 ( Ack)
43 ( C)
......
06 ( Ack)
----
04 (收 Eot)
15 ( NAK)
04 (收 Eot)
06 ( Ack)
43 ( C)
--
06 ( Ack)
Ymodom實現(xiàn)
該協(xié)議包括起始幀、數(shù)據(jù)幀、結束幀,狀態(tài)變量流轉的方向如下:
YmodemFileTransmit.h
status : StatusEstablish->StatusTransmit ->StatusFinishymodem.h
stage: StageNone-> StageEstablishing -> StageEstablished -> StageTransmitting->StageFinishing->StageFinished ->StageNone
code: CodeNone
里面數(shù)據(jù)幀要注意,傳1024或128規(guī)則如下:
- 數(shù)據(jù)大于128,則按1024傳;
- 數(shù)據(jù)小于128,則按128傳。
關于數(shù)據(jù)填充,看網上說是,以0x1A填充,但實際測試發(fā)現(xiàn),是按照00填充的。
整個指令流程如下:
- 接收方先發(fā) 43(C)
- 發(fā)送方發(fā) 文件名+文件大小
- 接收方發(fā) 06(Ack)
- 接收方發(fā) 43(C)
- 發(fā)送方開始一包包數(shù)據(jù)的發(fā)送,每發(fā)一包得等接收方回復 06(Ack)后再開始下一包
- 當發(fā)完最后一包數(shù)據(jù)后,發(fā)送方發(fā) 04 (Eot)
- 接收方發(fā) 15( NAK)
- 發(fā)送方再發(fā) 04 (Eot)
- 接收方發(fā) 06 ( Ack)
- 接收方發(fā) 43(C),開始下一個文件傳輸
- 如果不在發(fā)文件,則發(fā)送方發(fā)一包00數(shù)據(jù)
- 接收方發(fā) 06(Ack),結束傳輸。
在傳輸中,使用的指令主要如下:
CodeNone = 0x00,CodeSoh = 0x01, //128字節(jié)數(shù)據(jù)包;CodeStx = 0x02, //1024字節(jié)數(shù)據(jù)包;CodeEot = 0x04, //文件傳輸結束指令;CodeAck = 0x06, //接收正確指令;CodeNak = 0x15, //重傳當前數(shù)據(jù)包請求指令;CodeCan = 0x18, //取消傳輸指令,連續(xù)發(fā)送5個該命令,終止傳輸;CodeC = 0x43, //請求數(shù)據(jù)包CodeA1 = 0x41,CodeA2 = 0x61
QT實現(xiàn)
主要用得是Ymodem的開源庫函數(shù),然后對其進行二次開發(fā)。
開源庫的二次開發(fā)-1
里面最主要的一個變更是,在傳輸完成進行二次回復確定時,接收方會發(fā)一個Ack過來,此時會調用transmitStageFinishing()
,不難發(fā)現(xiàn)里面并沒有關于Ack 的處理,此時會調用default:
處理:
如果一直沒有發(fā)C指令,會不斷累加定時器調用次數(shù),由于設置一次定時器10ms,間隔5s后會重發(fā)04 (Eot)指令;如果超過設置的最大響應時間25s,會寫取消傳輸指令。當然正常是不會有問題的,但是在測試時候,由于輸入需要時間,時而會出現(xiàn)重發(fā)的情況,而且要注意這里的5s,是從第二次發(fā)完 EOT 后開始計算的。因此,對transmitStageFinishing()
增加Ack 處理 :
case CodeAck://sht-240813 add :避免發(fā)完ACK后C回復不及時,導致多發(fā)EOT指令{timeCount = 0;errorCount = 0;dataCount = 0;break;}
開源庫的二次開發(fā)-2
第二個最大變更是,關于讀取回復指令的長度設置,在部分的設備中,會存在回復指令加 0D 0A
的情況,用于分隔指令,因為接收方回復指令中存在連續(xù)發(fā)2個指令的情況,如果有了0D 0A
的加入,可以直接 以 06 0D 0A 43 0D 0A
方式發(fā)指令,當然也可以不用 0D 0A
,單純只是用 06 43
或者間隔一下時間分別發(fā),都可以。
既然出現(xiàn)了加 0D 0A
情況,那就要對讀取進行二次處理,在receivePacket()
中將read(&(rxBuffer[0]), 1)
修改為read(&(rxBuffer[0]), 3)
。
串口方式實現(xiàn)
最主要的就是串口收發(fā)一定要寫好,Ymodom 部分主要就是進行虛函數(shù)復寫就行。
QT += serialport
#include <QSerialPort>
QSerialPort * serialPort;
if (serialPort->open(QSerialPort::ReadWrite) == true){//成功打開串口return true;}else{//串口打開失敗return false;}
//讀寫指定長度len,存入buff
uint32_t YmodemFileTransmitSerial::read(uint8_t* buff, uint32_t len)
{return serialPort->read((char*)buff, len);
}uint32_t YmodemFileTransmitSerial::write(uint8_t* buff, uint32_t len)
{return serialPort->write((char*)buff, len);
}
TCP方式實現(xiàn)
QT +=network
#include <QTcpSocket>
QTcpSocket * tcpClient;
這里一定要注意,平常會有信號觸發(fā)的方式進行連接成功的判斷,但為了減少跳轉,以及代碼邏輯的統(tǒng)一,這邊采用了waitForConnected
去進行連接成功與否的判斷,設置的30000為等待連接時間,超過了則返回false。
tcpClient->connectToHost(targetAddr,serverPort);if(tcpClient->waitForConnected(30000)){//連接成功return true;}else{return false;}
這里也一定要注意,平常進行數(shù)據(jù)接收我們一般也是采用信號觸發(fā)的方式,但這邊不是,用得read
和write
,傳參分別是存儲信息的地址和讀取長度,返回實際讀取長度。當發(fā)來一共10個字節(jié),然后讀了3個,后面7個字節(jié)會緩存,可以下次讀,因此針對開源庫的二次開發(fā)-2主要就是影響這里,加了0D 0A
,在讀1字節(jié),就會有問題。
//-----------虛函數(shù)實現(xiàn),讀取內容----
uint32_t YmodemFileTransmitTcp::read(uint8_t *buff, uint32_t len)
{QByteArray array = tcpClient->read(len);uint32_t lenArray = array.size();uint32_t lenBuff = len;uint32_t length = qMin(lenArray, lenBuff);memcpy(buff, array, length);return length;
}
//-----------虛函數(shù)實現(xiàn),寫內容----
uint32_t YmodemFileTransmitTcp::write(uint8_t *buff, uint32_t len)
{int ret = tcpClient->write((char*)buff, len);return ret;
}
UDP方式實現(xiàn)
QT +=network
#include <QUdpSocket>
QUdpSocket* udpClient;
UDP也是一樣的情況,由于不連接通訊,倒是不用增加連接步驟,但是接收信息不用常用的信號觸發(fā)實現(xiàn),也是直接用read
和write
,其目的其實都是為了方便代碼編寫,更好使用Ymodom 庫,確保三種方式邏輯編寫規(guī)則統(tǒng)一。
//-----------虛函數(shù)實現(xiàn),讀取內容----
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;
}
//-----------虛函數(shù)實現(xiàn),寫內容----
uint32_t YmodemFileTransmit::write(uint8_t* buff, uint32_t len)
{QHostAddress targetAddr(serverIp);int ret = udpClient->writeDatagram((char*)buff, len, targetAddr, serverPort);return ret;
}
補充:文件讀取
在開發(fā)中,需要涉及到文件的讀取,為了方便后續(xù)的復用,這邊也做一個整理
- 找文件,存文件路徑
#include <QFileDialog>
#include <QMessageBox>void BootLoader::on_pushButtonBrowse_clicked()
{QString curPath = QDir::currentPath();ui->lineEditFilePath->setText(QFileDialog::getOpenFileName(this, u8"打開文件", curPath, u8"任意文件 (*.*)"));
}
- 讀文件
#include <QFile>
QFile* file;
YmodemFileTransmit::YmodemFileTransmit(QObject* parent) :QObject(parent),file(new QFile)
{
}
//-------------設置讀取文件名--------
void YmodemFileTransmit::setFileName(const QString& name)
{file->setFileName(name);
}
//獲取文件名 +文件大小
if(file->open(QFile::ReadOnly) == true) {QFileInfo fileInfo(*file);fileSize = fileInfo.size();fileCount = 0;//將文件名fileInfo.fileName().toLocal8Bit().data()存buffstrcpy((char*)buff, fileInfo.fileName().toLocal8Bit().data());//將文件大小QByteArray::number(fileInfo.size()).data())存buffstrcpy((char*)buff + fileInfo.fileName().toLocal8Bit().size() + 1, QByteArray::number(fileInfo.size()).data());}
//讀YMODEM_PACKET_1K_SIZE最大長度的內容存buff,返回讀取的實際長度。
//而且只要沒有file->close();,file->read 會移動文件游標,讀完一次后面接著讀fileCount += file->read((char*)buff, YMODEM_PACKET_1K_SIZE);
補充:QT 封裝成EXE
可以查看大神的博文【QT中如何生成導出.exe可執(zhí)行文件并打包給其他人使用】,里面講得很清晰。