1
0
mirror of https://bitbucket.org/librepilot/librepilot.git synced 2024-11-29 07:24:13 +01:00

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
This commit is contained in:
zedamota 2010-04-08 19:52:49 +00:00 committed by zedamota
parent cabd28cb4f
commit 877410e3ad
9 changed files with 928 additions and 0 deletions

View File

@ -0,0 +1,2 @@
LIBS += -l$$qtLibraryTarget(Qymodem)

View File

@ -0,0 +1,5 @@
#
TEMPLATE = subdirs
SUBDIRS = src \

View File

@ -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<dataEnd)
crc = UpdateCRC16(crc,*data++);
crc = UpdateCRC16(crc,0);
crc = UpdateCRC16(crc,0);
return crc&0xffffu;
}
uint8_t QymodemBase::Checksum(const char* data, size_t size)
{
int sum = 0;
const char* dataEnd = data+size;
while(data<dataEnd)
sum += *data++;
return sum&0xffu;
}
int QymodemBase::InChar(long timeout)
{
char c;
Port.setTimeout(timeout);
int result =(int)Port.read(&c,1);
if(result==1)
return c;
if(result==0)
return ErrorTimeout;
return result;
}
void QymodemBase::Cancel()
{
const char CancelString[] = { CAN,CAN,CAN,CAN,CAN };
Port.setTimeout(1000);
Port.write(CancelString,sizeof(CancelString));
}

View File

@ -0,0 +1,102 @@
#ifndef YMODEM_H
#define YMODEM_H
#include <qextserialport/src/qextserialport.h>
#include <QThread>
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

View File

@ -0,0 +1,406 @@
/**
@file
@brief Implementation of Y-Modem transmit protocol.
*/
#include "qymodem_TX.h"
#include <string.h> // 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<SendTimeout)
timeout = SendTimeout;
while(InChar(-1)>=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<timeoutStep)
return ErrorTimeout;
timeout -= timeoutStep;
}
ModeChar = c;
return 0;
}
/**
Send a single block of data.
A zero sized block terminates the transfer.
@param data The data to transfer.
@param size Size of data.
@return The number of transfered, or an error code on failure.
The number of bytes may be less than \a size.
@pre SendInitialise() must have been successful.
*/
int QymodemTx::SendBlock(const char* data, size_t size)
{
char block[1+2+1024+2]; // buffer to hold data in the block
int retryCount = 10; // number of attempts to send the block
bool waitForBlockACK = WaitForBlockACK;
change_mode:
size_t blockSize = (Use1KBlocks && size>=1024) ? 1024 : 128;
size_t dataSize = size<blockSize ? size : blockSize; // size of data to send in block
if(!dataSize)
{
// all bytes sent, so end transfer by sending a single EOT...
block[0] = EOT;
blockSize = 1;
waitForBlockACK = true;
}
else
{
// make block header...
block[0] = blockSize==1024 ? STX : SOH;
block[1] = BlockNumber&0xffu;
block[2] = (~BlockNumber)&0xffu;
// copy data for block (padding with EOF)...
memcpy(block+3,data,dataSize);
memset(block+3+dataSize,26,blockSize-dataSize);
// append checksum/crc...
if(SendCRC)
{
uint16_t crc = CRC16(block+3,blockSize);
blockSize += 3;
block[blockSize++] = (uint8_t)(crc>>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<outEnd)
{
char c = *fileName++;
if(c>='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 && len<lenEnd)
*out++ = *len++;
// check that buffer was big enough...
if(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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -0,0 +1,25 @@
#ifndef QYMODEMSEND_H
#define QYMODEMSEND_H
#include "qymodem_TX.h"
#include <QString>
#include <QFile>
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

View File

@ -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