From 877410e3adc19bafdbce3dc90412f4e54df391e6 Mon Sep 17 00:00:00 2001 From: zedamota Date: Thu, 8 Apr 2010 19:52:49 +0000 Subject: [PATCH] ymodem library to be used mainly with the GCS Uploader plug in. Uses qextserialport library and has regular and threaded sending. TODO:cleaning, documenting,crediting, error checking, and testing on LINUX. git-svn-id: svn://svn.openpilot.org/OpenPilot/trunk@441 ebee16cc-31ac-478f-84a7-5cbb03baadba --- ground/src/libs/qymodem/qymodem.pri | 2 + ground/src/libs/qymodem/qymodem.pro | 5 + ground/src/libs/qymodem/src/qymodem.cpp | 69 ++++ ground/src/libs/qymodem/src/qymodem.h | 102 +++++ ground/src/libs/qymodem/src/qymodem_TX.cpp | 406 ++++++++++++++++++++ ground/src/libs/qymodem/src/qymodem_TX.h | 108 ++++++ ground/src/libs/qymodem/src/qymodemsend.cpp | 192 +++++++++ ground/src/libs/qymodem/src/qymodemsend.h | 25 ++ ground/src/libs/qymodem/src/src.pro | 19 + 9 files changed, 928 insertions(+) create mode 100644 ground/src/libs/qymodem/qymodem.pri create mode 100644 ground/src/libs/qymodem/qymodem.pro create mode 100644 ground/src/libs/qymodem/src/qymodem.cpp create mode 100644 ground/src/libs/qymodem/src/qymodem.h create mode 100644 ground/src/libs/qymodem/src/qymodem_TX.cpp create mode 100644 ground/src/libs/qymodem/src/qymodem_TX.h create mode 100644 ground/src/libs/qymodem/src/qymodemsend.cpp create mode 100644 ground/src/libs/qymodem/src/qymodemsend.h create mode 100644 ground/src/libs/qymodem/src/src.pro diff --git a/ground/src/libs/qymodem/qymodem.pri b/ground/src/libs/qymodem/qymodem.pri new file mode 100644 index 000000000..52eb946e7 --- /dev/null +++ b/ground/src/libs/qymodem/qymodem.pri @@ -0,0 +1,2 @@ +LIBS += -l$$qtLibraryTarget(Qymodem) + diff --git a/ground/src/libs/qymodem/qymodem.pro b/ground/src/libs/qymodem/qymodem.pro new file mode 100644 index 000000000..277ca41bd --- /dev/null +++ b/ground/src/libs/qymodem/qymodem.pro @@ -0,0 +1,5 @@ +# +TEMPLATE = subdirs + +SUBDIRS = src \ + diff --git a/ground/src/libs/qymodem/src/qymodem.cpp b/ground/src/libs/qymodem/src/qymodem.cpp new file mode 100644 index 000000000..0ade27bb8 --- /dev/null +++ b/ground/src/libs/qymodem/src/qymodem.cpp @@ -0,0 +1,69 @@ + +/** +@file + +@brief Implementation of base class for YModemRx and YModemTx. +*/ + +#include "qymodem.h" + +uint16_t QymodemBase::UpdateCRC16(uint16_t crcIn, uint8_t byte) + { + uint32_t crc = crcIn; + uint32_t in = byte|0x100; + do + { + crc <<= 1; + in <<= 1; + if(in&0x100) + ++crc; + if(crc&0x10000) + crc ^= 0x1021; + } + while(!(in&0x10000)); + return crc&0xffffu; + } + + +uint16_t QymodemBase::CRC16(const char* data, size_t size) + { + uint32_t crc = 0; + const char* dataEnd = data+size; + while(data +#include + + +class QymodemBase: public QThread +{ + Q_OBJECT +signals: + void Error(QString,int); + void Information(QString,int); + +protected: + + inline QymodemBase(QextSerialPort& port) + : Port(port) + {} + + /** + Checksum a block of data. + + @param data Start of data to checksum. + @param size Size of data. + + @return Sum of bytes in data, modulo 256. + */ + uint8_t Checksum(const char* data, size_t size); + + /** + Calculate CRC for a block of data. + + @param data Start of data to checksum. + @param size Size of data. + + @return CRC of data. + */ + uint16_t CRC16(const char* data, size_t size); + + /** + Update CRC value by accumulating another byte of data. + + @param crcIn Previous CRC value. + @param byte A byte of data. + + @return Updated CRC value. + */ + uint16_t UpdateCRC16(uint16_t crcIn, uint8_t byte); + + /** + Receive a single character. + If the timeout period is exceeded, ErrorTimeout is returned. + + @param timeout Time in milliseconds to wait if no data available. + + @return The character received, or a negative error value if failed. + */ + int InChar(long timeout); + + /** + Send CANcel sequence. + */ + void Cancel(); + + /** + Enumeration of control characted used in communications protocol. + */ + enum ControlCharacters + { + SOH = 0x01, + STX = 0x02, + EOT = 0x04, + ACK = 0x06, + NAK = 0x15, + CAN = 0x18 + }; + + /** + Enumeration of possible error values. + */ + enum Error + { + ErrorTimeout = -200, /**< Timed out trying to communicate with other device */ + ErrorBlockRetriesExceded = -201, /**< A block could not be sent */ + + }; + +protected: + /** + The serial port to use for communications. + */ + + QextSerialPort& Port; + int percent; +}; + + +/** @} */ // End of group + +#endif // YMODEM_H diff --git a/ground/src/libs/qymodem/src/qymodem_TX.cpp b/ground/src/libs/qymodem/src/qymodem_TX.cpp new file mode 100644 index 000000000..2f00b928e --- /dev/null +++ b/ground/src/libs/qymodem/src/qymodem_TX.cpp @@ -0,0 +1,406 @@ + +/** +@file + +@brief Implementation of Y-Modem transmit protocol. +*/ + + +#include "qymodem_TX.h" + + +#include // for memcpy and memset + + +/** +Minimum time in milli-seconds to wait for response from receiver. +*/ +const unsigned SendTimeout = 11*1000; + + +/** +Process response from receiver. + +@param c Value received from InChar. + +@return One if received an ACKnowledge, + zero if received a Negative AcKnowledge, + or a negative error value. +*/ +int QymodemTx::ProcessResponse(int c) + { + if(c<0) + return c; + if(c==CAN) + { + if(CancelCount++) + return ErrorTranferTerminatedByReceiver; + return 0; + } + CancelCount = 0; + if(c==ACK) + return 1; + return 0; + } + + +/** +Begin the Y-Modem transfer. + +@param timeout Time in milliseconds to wait receiver to become ready. + +@return Zero if transfer initialisation was successful, or an error code on failure. +*/ +int QymodemTx::SendInitialise(unsigned timeout) + { + emit Information("Waiting for Receiver",QymodemTx::InfoSending); + if(timeout=0) // flush input buffers + {} + + CancelCount = 0; + + int c; + for(;;) + { + const unsigned timeoutStep = 10; + c = InChar(timeoutStep); + if(c=='G') + { + SendCRC = true; + WaitForBlockACK = false; + break; + } + else if(c=='C') + { + SendCRC = true; + WaitForBlockACK = true; + break; + } + else if(c==NAK) + { + SendCRC = false; + WaitForBlockACK = true; + break; + } + if(c<0 && c!=ErrorTimeout) + return c; + if(timeout=1024) ? 1024 : 128; + size_t dataSize = size>8); + block[blockSize++] = (uint8_t)crc; + } + else + { + uint8_t sum = Checksum(block+3,blockSize); + blockSize += 3; + block[blockSize++] = sum; + } + } + +do_retry: + // count attenpts... + if(!retryCount--) + return ErrorBlockRetriesExceded; + + char* out = block; + size_t outSize = blockSize; + for(;;) + { + // send some data... + Port.setTimeout(1000);; + int result = (int)Port.write(out,outSize); + if(result<0) + return result; // return error + if(result==0) + return ErrorTimeout; + + // adjust for data remaining... + out += result; + outSize -= result; + + // end if done... + if(!outSize) + break; + + // poll for signal from receiver... + result = ProcessResponse(InChar(10)); + if(result==ErrorTimeout) + continue; // nothing received + if(result<0) + return result; // return error + if(!result) + goto retry; // negative acknowledge received + } + + if(waitForBlockACK) + { + // wait for up to one second for block to be acknowledged... + int c = InChar(1000); + int result = ProcessResponse(c); + if(result<0) + return result; // return error + if(!result) + { + // negagtive acknowledge received... + if(c=='C' && !SendCRC) + { + // change to CRC mode if receiver sent 'C', and retry... + SendCRC = true; + goto change_mode; + } + goto retry; + } + } + else + { + // check for receiver sending a cancel byte... + int result = ProcessResponse(InChar(0)); + if(result<0) + { + if(result!=ErrorTimeout) + return result; // return error if it's not a timeout + } + else + { + // ignore other responses + } + } + + // block transferred OK... + ++BlockNumber; + return dataSize; + +retry: + while(InChar(500)>=0) // flush input buffers + {} + goto do_retry; + } + + +/** +Send data. +A zero sized block terminates the transfer. + +@param data The data to transfer. +@param size Size of data. + +@return Zero if successful, or a negative error value if failed. + +@pre SendInitialise() must have been successful. +*/ +int QymodemTx::SendData(const char* data, size_t size) + { + do + { + int result = SendBlock(data, size); + if(result<0) + return result; + data += result; + size -= result; + } + while(size); + return 0; + } + + +/** +Send an entire stread of data. + +@param in The stream of data to send. + +@return Zero if successful, or a negative error value if failed. + +@pre SendInitialise() must have been successful. +*/ +int QymodemTx::SendAll(InStream& in) + { + BlockNumber = 1; // first block to send is number one + size_t size; + do + { + // get data from input stream... + char data[1024]; + int result = in.In(data,sizeof(data),&percent); + if(result<0) + return ErrorInputStreamError; + + // send data... + + size = result; + result = SendData(data,size); + if(result<0) + return result; + } + while(size); // end when no more data left + return 0; + } + + +/** +Construct the data for the first block of a Y-Modem transfer. + +@param[out] buffer The buffer to store the constructed block. Size must be >=128 bytes. +@param fileName The name of the file being transferred. +@param fileSize The size of the file being transferred. + +@return Zero if successful, or a negative error value if failed. +*/ +int QymodemTx::MakeBlock0(char* buffer, const char* fileName, size_t fileSize) + { + // setup buffer for block 0... + char* out = buffer; + char* outEnd = buffer+128-1; + memset(buffer,0,128); + + // copy file name to block data... + while(out='A' && c<='Z') + c += 'a'-'A'; // convert name to lower-case + else if(c=='\\') + c = '/'; // convert back-slash to forward-slash + *out++ = c; + if(!c) + break; // end of name + } + + // convert fileSize to a decimal number... + char length[sizeof(size_t)*3+1]; // buffer big enough to hold length as decimal number + char* lenEnd = length+sizeof(length); + char* len = lenEnd; + do + { + *--len = '0'+fileSize%10; // prepend digit to buffer + fileSize /= 10; + } + while(fileSize); // end when all digits done + + // append file length to block data... + while(out=outEnd) + return ErrorFileNameTooLong; + + return 0; // OK + } + + +QymodemTx::QymodemTx(QextSerialPort& port) + : QymodemBase(port) + { + } + + +int QymodemTx::SendX(InStream& in, unsigned timeout, bool kMode) + { + Use1KBlocks = kMode; + int result = SendInitialise(timeout); + if(result<0) + return result; + return SendAll(in); + } + + +int QymodemTx::SendY(const char* fileName, size_t size, InStream& in, unsigned timeout) + { + Use1KBlocks = true; + char buffer[128]; + int result = MakeBlock0(buffer,fileName,size); + if(result<0) + return result; + + result = SendInitialise(timeout); + if(result<0 && result!=ErrorBlockRetriesExceded) + return result; + emit Information("Sending "+QString(fileName),QymodemTx::InfoSending); + BlockNumber = 0; + result = SendBlock(buffer,sizeof(buffer)); + if(result<0) + return result; + + result = InChar(SendTimeout); + if(result<0) + return result; + if(result!=ModeChar) + return ErrorReceiverNotBehaving; + + result = SendAll(in); + if(result<0) + return result; + + result = InChar(SendTimeout); + if(result<0) + return result; + if(result!=ModeChar) + return ErrorReceiverNotBehaving; + + memset(buffer,0,sizeof(buffer)); + BlockNumber = 0; + result = SendBlock(buffer,sizeof(buffer)); + if(result<0) + return result; + + return 0; + } + + diff --git a/ground/src/libs/qymodem/src/qymodem_TX.h b/ground/src/libs/qymodem/src/qymodem_TX.h new file mode 100644 index 000000000..d3f9b36ea --- /dev/null +++ b/ground/src/libs/qymodem/src/qymodem_TX.h @@ -0,0 +1,108 @@ + +/** +@file + +@brief Y-Modem transmit protocol. +*/ +#ifndef YMODEM_TX_H +#define YMODEM_TX_H +#include "qymodem.h" + +/** +@brief Y-Modem transmiter object. +@ingroup ymodem +*/ +class QymodemTx : public QymodemBase + { +public: + /** + Construct a Y-Modem object which will transmit data over the given port. + + @param port The port. + */ + QymodemTx(QextSerialPort& port); + + /** + Abstract class representing a stream of data being read. + */ + + class InStream + { + public: + /** + Read data from the stream. + + @param[out] data Pointer to buffer to hold data read from stream. + @param size Maximum size of data to read. + + @return Number of bytes successfully read, or a negative error value if failed. + */ + virtual int In(char* data, size_t size, int * percent) =0; + + /** + Empty destructor to avoid compiler warnings. + */ + inline virtual ~InStream() {} + }; + + /** + Send data using X-Modem. + + @param in The stream of data to send. + @param timeout Time in milliseconds to wait receiver to become ready. + @param kMode False to use 128 byte blocks, true to use 1kB blocks + @return Zero if transfer was successful, or a negative error value if failed. + */ + int SendX(InStream& in, unsigned timeout, bool kMode); + + /** + Send data using Y-Modem. + + @param fileName The name of the file being transferred. + @param size The size of the data being transferred. + @param in The stream of data to send. + @param timeout Time in milliseconds to wait receiver to become ready. + + @return Zero if transfer was successful, or a negative error value if failed. + */ + int SendY(const char* fileName, size_t size, InStream& in, unsigned timeout); + + /** + Enumeration of possible error values. + */ + enum TxError + { + ErrorInputStreamError = -300, /**< Error with input stream */ + ErrorReceiverNotBehaving = -301, /**< Unexpected data received */ + ErrorTranferTerminatedByReceiver= -302, /**< Transfer was terminated by receiver */ + ErrorFileNameTooLong = -303, /**< File name was too long to be transmitted */ + ErrorFileNotFound= -303, + ErrorCoulNotOpenPort= -304, + ErrorFileTransmissionInProgress = -305 + + }; + enum Info + { + InfoSending= -100, + InfoSent=-101, + InfoWaitingforReceiver=-102 + }; + +private: + int SendInitialise(unsigned timeout); + int SendBlock(const char* data, size_t size); + int SendData(const char* data, size_t size); + int SendAll(InStream& in); + int MakeBlock0(char* buffer, const char* fileName, size_t fileSize); + int ProcessResponse(int c); +private: + size_t BlockNumber; + bool SendCRC; + bool WaitForBlockACK; + bool Use1KBlocks; + uint8_t ModeChar; + int CancelCount; + }; + + +#endif // YMODEM_TX_H diff --git a/ground/src/libs/qymodem/src/qymodemsend.cpp b/ground/src/libs/qymodem/src/qymodemsend.cpp new file mode 100644 index 000000000..932962df1 --- /dev/null +++ b/ground/src/libs/qymodem/src/qymodemsend.cpp @@ -0,0 +1,192 @@ +#include "qymodemsend.h" + + +QymodemSend::QymodemSend(QextSerialPort& port) + : QymodemTx(port) + { + percent=0; + } +int QymodemSend::PercentSend() +{ + return percent; +} + +/** +Class for presenting a file as a input stream. +*/ +class QymodemSend::InFile : public QymodemTx::InStream +{ +public: + InFile() + : File(0) + { + } + /** + Open stream for reading a file. + + @param fileName Name of the file to read. + + @return Zero if successful, or a negative error value if failed. + */ + int Open(const char* fileName) + { + File = fopen(fileName,"rb"); + if(!File) + return QymodemTx::ErrorInputStreamError; + // find file size... + if(fseek(File,0,SEEK_END)) + { + fclose(File); + return QymodemTx::ErrorInputStreamError; + } + TotalSize = ftell(File); + if(fseek(File,0,SEEK_SET)) + { + fclose(File); + return QymodemTx::ErrorInputStreamError; + } + TransferredSize = 0; + return 0; + } + + /** + Close the stream. + */ + void Close() + { + if(File) + { + fclose(File); + File = 0; + } + } + + /** + Return the size of the file. + + @return File size. + */ + inline size_t Size() + { + return TotalSize; + } + + /** + Read data from the stream. + + @param[out] data Pointer to buffer to hold data read from stream. + @param size Maximum size of data to read. + + @return Zero if successful, or a negative error value if failed. + */ + int In(char* data, size_t size, int * percent) + { + *percent = TotalSize ? ((uint64_t)TransferredSize*(uint64_t)100)/(uint64_t)TotalSize : 0; + + //printf("%8d bytes of %d - %3d%%\r",TransferredSize, TotalSize, percent); + + fflush(stdout); + size=fread(data,sizeof(uint8_t),size,File); + if(size) + { + TransferredSize += size; + return size; + } + if(TransferredSize!=TotalSize) + return QymodemTx::ErrorInputStreamError; + return 0; + } +private: + FILE* File; + size_t TotalSize; + size_t TransferredSize; +}; + + +/** +Class for presenting a file as a output stream. +*/ + + + +/** +Send the file. + +@param port The serial port to use. +*/ +void QymodemSend::Send() +{ + + InFile source; + int error = source.Open(FileName); + if(error) + emit Error("Can't open file " + QString(FileName),error); + + + unsigned Timeout=30000; + error = SendY(FileName,source.Size(),source,Timeout); + if(error) + { + emit Error("Error during file transfer, error "+QString(error),error); + } + else + { + emit Information("Sent OK",QymodemSend::InfoSent); + } + source.Close(); +} + +int QymodemSend::SendFile(QString filename) +{ + QFile file; + if(!file.exists(filename)) + { + emit Error("File not found",QymodemSend::ErrorFileNotFound); + return QymodemSend::ErrorFileNotFound; + } + if(!Port.open(QIODevice::ReadWrite| QIODevice::Unbuffered)) + { + emit Error("Could not open port",QymodemSend::ErrorCoulNotOpenPort); + return QymodemSend::ErrorCoulNotOpenPort; + } + QByteArray a=filename.toLocal8Bit(); + FileName=a.constData(); + Send(); + Port.close(); + return 0; + +} + +int QymodemSend::SendFileT(QString filename) +{ + if(!isRunning()) + { + FileNameT=filename; + start(); + } + else + { + return QymodemSend::ErrorFileTransmissionInProgress; + } + return 0; +} +void QymodemSend::run() +{ + QFile file; + if(!file.exists(FileNameT)) + { + emit Error("File not found",QymodemSend::ErrorFileNotFound); + return; + } + if(!Port.open(QIODevice::ReadWrite| QIODevice::Unbuffered)) + { + emit Error("Could not open port",QymodemSend::ErrorCoulNotOpenPort); + return; + } + QByteArray a=FileNameT.toLocal8Bit(); + FileName=a.constData(); + Send(); + Port.close(); + return; + +} diff --git a/ground/src/libs/qymodem/src/qymodemsend.h b/ground/src/libs/qymodem/src/qymodemsend.h new file mode 100644 index 000000000..ca2eedb05 --- /dev/null +++ b/ground/src/libs/qymodem/src/qymodemsend.h @@ -0,0 +1,25 @@ +#ifndef QYMODEMSEND_H +#define QYMODEMSEND_H +#include "qymodem_TX.h" +#include +#include + + +class QymodemSend:public QymodemTx +{ + +public: + QymodemSend(QextSerialPort& port); + int SendFile(QString filename); + int SendFileT(QString filename); + int PercentSend(); + +private: + void run(); + const char* FileName; + void Send(); + class InFile; + QString FileNameT; + +}; +#endif // QYmodemSend_H diff --git a/ground/src/libs/qymodem/src/src.pro b/ground/src/libs/qymodem/src/src.pro new file mode 100644 index 000000000..962d25a44 --- /dev/null +++ b/ground/src/libs/qymodem/src/src.pro @@ -0,0 +1,19 @@ +include(../../../openpilotgcslibrary.pri) +PROJECT = qymodem +TEMPLATE = lib +DEFINES += QYMODEM_LIBRARY +TARGET = Qymodem +# CONFIG += staticlib +SOURCES += qymodem.cpp \ + qymodem_TX.cpp \ + qymodemsend.cpp +HEADERS += qymodem_TX.h \ + qymodem.h \ + qymodemsend.h + + + +CONFIG(debug, debug|release):LIBS += -lqextserialportd +else:LIBS += -lqextserialport +win32:LIBS += -lsetupapi +