/* This program is distributed under the terms of the 'MIT license'. The text of this licence follows... Copyright (c) 2006 J.D.Medhurst (a.k.a. Tixy) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** @file @brief Command-line progran for Y-Modem file transfer. */ #include "../../common/common.h" #include "../ymodem_tx.h" #include "../ymodem_rx.h" #include #include #include #include /** @defgroup ymodem_test Test - Command line program incorporating Y-Modem source @ingroup ymodem @brief Program for transferring files over a serial port using the Y-Modem (or X-Modem) transmission protocol. This acts as a test program for the Y-Modem implementation and is a useful utility in its own right. It also supports receiving a 'log' after transmitting files; this can be used when sending a file to a development board and receiving instrumentation output which comes back on same port.
Usage: ymodem [OPTION]... FILE [DST-FILE]
  -s[y|x|k]       Send file.
                  If 'y' is specifieied Y-Modem protocol is used. (Default)
                  If 'x' is specifieied X-Modem protocol is used.
                  If 'k' is specifieied X-Modem 1K protocol is used.
  -r[y|g|x|c]     Receive file.
                  If 'y' is specifieied Y-Modem protocol is used. (Default)
                  If 'g' is specifieied Y-Modem G protocol is used.
                  If 'x' is specifieied X-Modem protocol is used.
                  If 'c' is specifieied X-Modem CRC protocol is used.
  -pPORT          Use communications port PORT.
  -bBAUD          Set baud rate for port to BAUD.
  -tTIMEOUT       Set timeout for transfer start (in seconds).
  -lfLOG          Capture log after transfer and write it to file LOG.
  -ltLOGTIMEOUT   Timeout for log capture (in seconds).
  -lxSTRING       End log capture when STRING is received.
  -le             Echo the captured log to STDOUT.

FILE is optional when receiving a file by Y-Modem: options -ry or -rg
DST-FILE is the name sent to receiver when using Y-Modem. Default is FILE.
Program exit values: - 0 (zero) if transfer succeeded. - 1 (one) if the command line is invalid. - A negative value from SerialPort::Error, YModem::Error, YModemTx::TxError, YModemRx::RxError, or #Error. @version 2007-01-17 - Added -le option to echo the captured log to STDOUT. @version 2007-05-28 - Enabled neither send nor recieve to be specified, this enables the program to be abused to just capture a log from a serial port. @{ */ #define DEFAULT_PORT 1 /**< Default value for #PortNum */ #define DEFAULT_BAUD 115200 /**< Default value for #PortBaud */ #define DEFAULT_TIMEOUT 30 /**< Default value (in seconds) for #Timeout */ #define DEFAULT_LOG_TIMEOUT 10 /**< Default value (in seconds) for #LogTimeout */ #define MAX_LOG_END_STRING_LENGTH 128 /**< Maximum length for #LogEndString */ const char* ProgramName; /**< Name of this program */ unsigned PortNum = DEFAULT_PORT; /**< Serial port number to use */ unsigned PortBaud = DEFAULT_BAUD; /**< Baud rate for serial port */ unsigned Timeout = DEFAULT_TIMEOUT*1000; /**< Timeout (in milliseconds) before aborting transfer */ bool XModemFlag = false; /**< True for X-Modem, false for Y-Modem */ bool CrcFlag = false; /**< True for X-Modem CRC mode */ bool KModeFlag = false; bool GModeFlag = false; /**< True for Y-Modem G mode */ bool SendFlag = false; /**< True if sending file */ bool ReceiveFlag = false; /**< True if receiving file */ const char* FileName = 0; /**< Name of file to be transferred */ const char* DstFileName = 0; /**< File name to send to receiver in Y-Modem transer */ const char* LogFileName = 0; /**< Name of file for 'capture log' */ unsigned LogTimeout = DEFAULT_LOG_TIMEOUT*1000; /**< Timeout (in milliseconds) before aborting 'capture log' */ const char* LogEndString = 0; /**< String to terminate 'capture log' */ size_t LogEndStringLength = 0; /**< Length of #LogEndString */ bool LogEcho = false; /**< True if log should be echoed to STDOUT */ enum Error { ErrorNoMemory = -500, /**< Insufficient memory */ ErrorLogFileError = -501 /**< Error occured creating or writing to 'capture log' */ }; #define STRINGIFY2(a) #a /**< Helper for #STRINGIFY */ #define STRINGIFY(a) STRINGIFY2(a) /**< Convert \a a into a quoted string */ /** Display help message, then exit. */ void Help() { printf( "Usage: %s [OPTION]... FILE [DST-FILE]\n" "Tranfer a file using X-Modem or Y-Modem protocols\n\n" " -s[y|x|k] Send file.\n" " If 'y' is specifieied Y-Modem protocol is used. (Default)\n" " If 'x' is specifieied X-Modem protocol is used.\n" " If 'k' is specifieied X-Modem 1K protocol is used.\n" " -r[y|g|x|c] Receive file.\n" " If 'y' is specifieied Y-Modem protocol is used. (Default)\n" " If 'g' is specifieied Y-Modem G protocol is used.\n" " If 'x' is specifieied X-Modem protocol is used.\n" " If 'c' is specifieied X-Modem CRC protocol is used.\n" " -pPORT Use communications port PORT. Default is " STRINGIFY(DEFAULT_PORT) "\n" " -bBAUD Set baud rate for port to BAUD. Default is " STRINGIFY(DEFAULT_BAUD) "\n" " -tTIMEOUT Set timeout for transfer start (in seconds). Default is " STRINGIFY(DEFAULT_TIMEOUT) "\n" " -lfLOG Capture log after transfer and write it to file LOG\n" " -le Echo the captured log to STDOUT.\n" " -ltLOGTIMEOUT Timeout for log capture (in seconds). Default is " STRINGIFY(DEFAULT_LOG_TIMEOUT) "\n" " -lxSTRING End log capture when STRING is received\n" "\n" "FILE is optional when receiving a file by Y-Modem: options -ry or -rg\n" "DST-FILE is the name sent to receiver when using Y-Modem. Default is FILE.\n" ,ProgramName ); exit(1); } /** Print message when a bad command line is detected. Then do exit(1); . @param format Format string a la printf. @param ... Arguments for format sting, a la printf. */ void UsageError(const char *format, ...) { va_list args; va_start(args,format); vfprintf(stderr,format,args); va_end(args); fprintf(stderr,"\nTry '%s -h' for more information.\n",ProgramName); exit(1); } /** Print message when a error is detected. Then do exit(error); . @param error The error number. @param format Format string a la printf. @param ... Arguments for format sting, a la printf. */ void Error(int error,const char *format, ...) { va_list args; va_start(args,format); vfprintf(stderr,format,args); va_end(args); if(error<0) fprintf(stderr,". Error %d",error); fprintf(stderr,"\n"); exit(error); } /** @brief Parse an integer command-line argument. This converts a decimal string into an unsigned integer. @param argStr The string to convert. @param[out] arg The value of the converted number. @return True if conversion was successful, false otherwise. */ bool GetIntArg(const char* argStr, unsigned& arg) { char* argEnd; unsigned long val = strtoul(argStr,&argEnd,10); if(*argEnd) return false; arg = (unsigned)val; return true; } /** @brief Parse an command-line timeout argument. This converts a decimal string representing a number of seconds into an unsigned integer representing milliseconds. @param argStr The string to convert. @param[out] arg The value of the converted timeout. @return True if conversion was successful, false otherwise. */ bool GetTimeoutArg(const char* argStr, unsigned& arg) { if(!GetIntArg(argStr,arg)) return false; // convert to milliseconds... if(arg<(~(unsigned)0)/1000u) arg *= 1000; else arg = ~(unsigned)0; return true; } /** Parse all of the command-line arguments. @param argc The number of arguments. @param argv Array of argument strings. */ void ParseArgs(int argc, char** argv) { ProgramName = *argv; // parse arguments... while(--argc) { char* arg = *++argv; if(arg[0]=='"') { // remove "" from around argument... int len = strlen(arg); if(arg[len-1]=='"') { arg[len-1] = 0; ++arg; *argv = arg; } } if(arg[0]!='-') { // arg doesn't start with '-' so it must be the name of the file... if(DstFileName) UsageError("Too many file arguments"); if(FileName) DstFileName = arg; else FileName = arg; continue; } ++arg; // skip initial '-' switch(*arg++) { case 'p': if(!GetIntArg(arg,PortNum)) UsageError("Invalid PORT argument"); break; case 'b': if(!GetIntArg(arg,PortBaud)) UsageError("Invalid BAUD argument"); break; case 't': if(!GetTimeoutArg(arg,Timeout)) UsageError("Invalid TIMEOUT argument"); break; case 'h': Help(); case 's': SendFlag = true; XModemFlag = false; if(arg[0]!=0) { if(arg[1]!=0) goto invalid; if(arg[0]=='y') ; // default else if(arg[0]=='x') XModemFlag = true, KModeFlag = false; else if(arg[0]=='k') XModemFlag = true, KModeFlag = true; else goto invalid; } break; case 'r': ReceiveFlag = true; XModemFlag = false; GModeFlag = false; if(arg[0]!=0) { if(arg[1]!=0) goto invalid; if(arg[0]=='y') ; // default else if(arg[0]=='g') GModeFlag = true; else if(arg[0]=='x') CrcFlag = false, XModemFlag = true; else if(arg[0]=='c') CrcFlag = true, XModemFlag = true; else goto invalid; } break; case 'l': switch(*arg++) { case 'f': LogFileName = arg; break; case 'e': LogEcho = true; break; case 't': if(!GetTimeoutArg(arg,LogTimeout)) UsageError("Invalid LOGTIMEOUT argument"); break; case 'x': LogEndStringLength = strlen(arg); LogEndString = arg; if(LogEndStringLength>MAX_LOG_END_STRING_LENGTH) UsageError("String too long in -lx option. Max length is " STRINGIFY(MAX_LOG_END_STRING_LENGTH) ); break; default: goto invalid; } break; default: goto invalid; } continue; } return; invalid: UsageError("Invalid option '%s'",*argv); } /** Class for presenting a file as a input stream. */ class InFile : public YModemTx::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 YModemTx::ErrorInputStreamError; // find file size... if(fseek(File,0,SEEK_END)) { fclose(File); return YModemTx::ErrorInputStreamError; } TotalSize = ftell(File); if(fseek(File,0,SEEK_SET)) { fclose(File); return YModemTx::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(uint8_t* data, size_t size) { int 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 YModemTx::ErrorInputStreamError; return 0; } private: FILE* File; size_t TotalSize; size_t TransferredSize; }; /** Class for presenting a file as a output stream. */ class OutFile : public YModemRx::OutStream { public: OutFile() : File(0) { } /** Open stream for writing to a file. If no data has yet been written to the stream, this function may be called a second time in order to update the file size. @param fileName Name of the file to write. @param size Size of data being written to a file. @return Zero if successful, or a negative error value if failed. */ int Open(const char* fileName, size_t size) { if(File) { if(TransferredSize) return YModemRx::ErrorOutputStreamError; } else { File = fopen(fileName,"w+b"); if(!File) return YModemRx::ErrorOutputStreamError; printf("Receiving %s...\n",fileName); } TotalSize = size; TransferredSize = 0; return 0; } /** Close the stream. */ void Close() { if(File) { fclose(File); File = 0; } } /** Write data to the stream. @param data Pointer to data to write. @param size Size of data. @return Zero if successful, or a negative error value if failed. */ int Out(const uint8_t* data, size_t size) { if(!File) return YModemRx::ErrorOutputStreamError; // we've not been Open()ed // show progress... if(TotalSize) { int percent = TotalSize ? ((uint64_t)TransferredSize*(uint64_t)100)/(uint64_t)TotalSize : 0; printf("%8d bytes of %d - %3d%%\r",TransferredSize, TotalSize, percent); fflush(stdout); } else { printf("%8d bytes\r",TransferredSize); fflush(stdout); } // limit data to size of file... if(TotalSize) { size_t maxSize = TotalSize-TransferredSize; if(size>maxSize) size = maxSize; if(!maxSize) return 0; // end } // write data... if(size!=fwrite(data,sizeof(uint8_t),size,File)) return YModemRx::ErrorOutputStreamError; // write failed TransferredSize += size; return size; } private: FILE* File; size_t TotalSize; size_t TransferredSize; }; FILE* LogFile = 0; /**< Handle of the 'capture log' file */ /** Capture data from the serial port and write it to the 'capture log' file. Log capture ends either when no data has been received for #LogTimeout milliseconds, or when characters matching #LogEndString are found. @param port The serial port to receive data from. */ void CaptureLog(SerialPort* port) { printf("Capturing log to %s...\n",LogFileName); fflush(stdout); uint8_t logBuffer[1024+MAX_LOG_END_STRING_LENGTH]; size_t bufferOffset = 0; for(;;) { uint8_t* start = logBuffer+bufferOffset; uint8_t* end = logBuffer+sizeof(logBuffer); size_t length = end-start; int result = port->In(start,length,LogTimeout); if(!result) { fclose(LogFile); printf("Log capture timeout.\n"); return; } if(result<0) { fclose(LogFile); Error(result,"Error during log capture"); } // save data to log... length = result; end = start+length; if(fwrite(start,1,length,LogFile)!=length) { fclose(LogFile); Error(ErrorLogFileError,"File error during log capture"); } if(LogEcho) { #ifndef WIN32 fwrite(start,1,length,stdout); #else // Hack for windows because it expands LF character to CR+LF for(size_t i=0; iOpen(PortNum); if(error) Error(error,"Can't open port"); error = port->Initialise(PortBaud); if(error) Error(error,"Can't initialise port"); if(ReceiveFlag) Receive(port); else { if(LogFileName) { LogFile = fopen(LogFileName,"w+b"); if(!LogFile) Error(ErrorLogFileError,"Can't create LogFile file '%s'",LogFileName); } if(SendFlag || FileName) Send(port); if(LogFileName) CaptureLog(port); } port->Close(); delete port; return 0; } /** @} */ // End of group