diff options
| author | Nick Van Doorn <vandoorn.nick@gmail.com> | 2018-09-10 23:23:21 -0700 |
|---|---|---|
| committer | Nick Van Doorn <vandoorn.nick@gmail.com> | 2018-09-10 23:23:21 -0700 |
| commit | 83639bdffb6b9355a29d2b2e55807aa3b6837bbd (patch) | |
| tree | e5858b146a3901880e5babaa89aacbe0979f0552 /upload/xmodem.c | |
Initial commit
Diffstat (limited to 'upload/xmodem.c')
| -rw-r--r-- | upload/xmodem.c | 1879 |
1 files changed, 1879 insertions, 0 deletions
diff --git a/upload/xmodem.c b/upload/xmodem.c new file mode 100644 index 0000000..dd471d7 --- /dev/null +++ b/upload/xmodem.c @@ -0,0 +1,1879 @@ +////////////////////////////////////////////////////////////////////////////// +// // +// _ // +// __ __ _ __ ___ ___ __| | ___ _ __ ___ ___ // +// \ \/ /| '_ ` _ \ / _ \ / _` | / _ \| '_ ` _ \ / __| // +// > < | | | | | || (_) || (_| || __/| | | | | | _| (__ // +// /_/\_\|_| |_| |_| \___/ \__,_| \___||_| |_| |_|(_)\___| // +// // +// // +////////////////////////////////////////////////////////////////////////////// +// // +// Copyright (c) 2012 by S.F.T. Inc. - All rights reserved // +// Use, copying, and distribution of this software are licensed according // +// to the GPLv2, LGPLv2, or BSD license, as appropriate (see COPYING) // +// // +////////////////////////////////////////////////////////////////////////////// + +// XMODEM adapted for arduino and POSIX systems. Windows code incomplete + +#include "xmodem.h" + +// special I/O when linked into 'SFTARDCAL' application +#ifdef SFTARDCAL +int my_read(SERIAL_TYPE iFile, void* pBuf, int cbBuf); +int my_write(SERIAL_TYPE iFile, const void* pBuf, int cbBuf); +void my_flush(SERIAL_TYPE iFile); +#endif // SFTARDCAL + +// internal structure definitions + +// Windows requires a different way of specifying structure packing +#ifdef WIN32 +#define PACKED +#pragma pack(push, 1) +#else // POSIX, ARDUINO +#define PACKED __attribute__((__packed__)) +#endif // WIN32 vs THE REST OF THE WORLD + +#define _SOH_ 1 /* start of packet - note XMODEM-1K uses '2' */ +#define _EOT_ 4 +#define _ENQ_ 5 +#define _ACK_ 6 +#define _NAK_ 21 /* NAK character */ +#define _CAN_ 24 /* CAN character CTRL+X */ + +/** \file xmodem.c + * \brief main source file for S.F.T. XMODEM library + * + * S.F.T. XMODEM library +**/ + +/** \ingroup xmodem_internal + * \brief Structure defining an XMODEM CHECKSUM packet + * +\code +typedef struct _XMODEM_BUF_ +{ + char cSOH; // ** SOH byte goes here ** + unsigned char aSEQ, aNotSEQ; // ** 1st byte = seq#, 2nd is ~seq# ** + char aDataBuf[128]; // ** the actual data itself! ** + unsigned char bCheckSum; // ** checksum gets 1 byte ** +} PACKED XMODEM_BUF; +\endcode + * +**/ +typedef struct _XMODEM_BUF_ { + char cSOH; ///< SOH byte goes here + unsigned char aSEQ, aNotSEQ; ///< 1st byte = seq#, 2nd is ~seq# + char aDataBuf[128]; ///< the actual data itself! + unsigned char bCheckSum; ///< checksum gets 1 byte +} PACKED XMODEM_BUF; + +/** \ingroup xmodem_internal + * \brief Structure defining an XMODEM CRC packet + * +\code +typedef struct _XMODEMC_BUF_ +{ + char cSOH; // ** SOH byte goes here ** + unsigned char aSEQ, aNotSEQ; // ** 1st byte = seq#, 2nd is ~seq# ** + char aDataBuf[128]; // ** the actual data itself! ** + unsigned short wCRC; // ** CRC gets 2 bytes, high endian ** +} PACKED XMODEMC_BUF; +\endcode + * +**/ +typedef struct _XMODEMC_BUF_ { + char cSOH; ///< SOH byte goes here + unsigned char aSEQ, aNotSEQ; ///< 1st byte = seq#, 2nd is ~seq# + char aDataBuf[128]; ///< the actual data itself! + unsigned short wCRC; ///< CRC gets 2 bytes, high endian +} PACKED XMODEMC_BUF; + +#ifdef WIN32 +// restore default packing +#pragma pack(pop) +#endif // WIN32 + +/** \ingroup xmodem_internal + * \brief Structure that identifies the XMODEM communication state + * +\code +typedef struct _XMODEM_ +{ + SERIAL_TYPE ser; // identifies the serial connection, data type is +OS-dependent + FILE_TYPE file; // identifies the file handle, data type is OS-dependent + union + { + XMODEM_BUF xbuf; // XMODEM CHECKSUM buffer + XMODEMC_BUF xcbuf; // XMODEM CRC buffer + } buf; // union of both buffers, total length 133 bytes + unsigned char bCRC; // non-zero for CRC, zero for checksum +} XMODEM; +\endcode + * +**/ +typedef struct _XMODEM_ { + SERIAL_TYPE + ser; ///< identifies the serial connection, data type is OS-dependent + FILE_TYPE file; ///< identifies the file handle, data type is OS-dependent + + union { + XMODEM_BUF xbuf; ///< XMODEM CHECKSUM buffer + XMODEMC_BUF xcbuf; ///< XMODEM CRC buffer + } buf; ///< union of both buffers, total length 133 bytes + + unsigned char bCRC; ///< non-zero for CRC, zero for checksum + +} XMODEM; + +#ifdef DEBUG_CODE +static char szERR[32]; // place for error messages, up to 16 characters + +const char* XMGetError(void) { + return szERR; +} +#endif // DEBUG_CODE + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) +void debug_dump_buffer(int iDir, const void* pBuf, int cbBuf) { + int i1, i2; + const unsigned char *p1, *p2; + + if (cbBuf <= 0) { + return; + } + + p1 = p2 = (const unsigned char*)pBuf; + + for (i1 = 0, i2 = 0; i1 <= cbBuf; i1++, p1++) { + if (!i1 || i2 >= 16 || i1 == cbBuf) { + if (i1) { + while (i2 < 16) { + fputs(" ", stderr); // fill up spaces where data would be + i2++; + } + + fputs(" : ", stderr); + + while (p2 < p1) { + if (*p2 >= 32 && *p2 <= 127) { + fputc(*p2, stderr); + } else { + fputc('.', stderr); + } + + p2++; + } + + fputc('\n', stderr); + } + + if (!i1 && iDir > 0) { + fputs("--> ", stderr); + } else if (!i1 && iDir < 0) { + fputs("<-- ", stderr); + } else { + fputs(" ", stderr); + } + + i2 = 0; + p2 = p1; // make sure + } + + if (i1 < cbBuf) { + if (!i2) { + fprintf(stderr, "%02x: %02x", i1, *p1); + } else { + fprintf(stderr, ", %02x", *p1); + } + + i2++; + } + } + + fputc('\n', stderr); + fflush(stderr); +} +#endif // STAND_ALONE, DEBUG_CODE + +// char iBinaryTransfer = 0, iDisableRXOVER = 0; + +/** \ingroup xmodem_internal + * \brief Calculate checksum for XMODEM packet + * + * \param lpBuf A pointer to the XMODEM data buffer + * \param cbBuf The length of the XMODEM data buffer (typically 128) + * \return An unsigned char value to be assigned to the 'checksum' element in + *the XMODEM packet + * +**/ +unsigned char CalcCheckSum(const char* lpBuf, short cbBuf) { + short iC, i1; + + iC = 0; + + for (i1 = 0; i1 < cbBuf; i1++) { + iC += lpBuf[i1]; + } + + return (unsigned char)(iC & 0xff); +} + +/** \ingroup xmodem_internal + * \brief Calculate checksum for XMODEM packet + * + * \param sVal An unsigned short integer to be made 'high endian' by flipping + *bytes (as needed) + * \return A (possibly) byte-flipped high-endian unsigned short integer + * + * This function assumes low-endian for Arduino, and performs a universal + *operation + * for 'indeterminate' architectures. +**/ +static unsigned short my_htons(unsigned short sVal) { + union { + unsigned char aVal[2]; + unsigned short sVal; + } a, b; + + // tweeked for size and speed. enjoy. + + b.sVal = sVal; + +#ifdef ARDUINO + + a.aVal[0] = b.aVal[1]; // no math involved, pre-optimized code + a.aVal[1] = b.aVal[0]; + +#else + + a.aVal[0] = (unsigned char)(sVal >> 8); // less optimized but universal code + a.aVal[1] = (unsigned char)(sVal & 0xff); + +#endif // ARDUINO + + return a.sVal; +} + +/** \ingroup xmodem_internal + * \brief Calculate 16-bit CRC for XMODEM packet + * + * \param lpBuf A pointer to the XMODEM data buffer + * \param cbBuf The length of the XMODEM data buffer (typically 128) + * \return A high-endian 16-bit (unsigned short) value to be assigned to the + *'CRC' element in the XMODEM packet + * + * This method uses the 'long way' which is SMALLER CODE for microcontrollers, + *but eats up a bit more CPU. + * Otherwise, you'd have to pre-build the 256 byte table and use "the table + *lookup" method. +**/ +unsigned short CalcCRC(const char* lpBuf, short cbBuf) { + unsigned short wCRC; + short i1, i2, iAX; + char cAL; + + // ** this function returns 2-byte string containing + // ** the CRC calculation result, as high endian + + wCRC = 0; + + for (i1 = 0; i1 < cbBuf; i1++) { + cAL = lpBuf[i1]; + + iAX = (unsigned short)cAL << 8; + + wCRC = iAX ^ wCRC; + + for (i2 = 0; i2 < 8; i2++) { + iAX = wCRC; + + if (iAX & 0x8000) { + wCRC <<= 1; + wCRC ^= 0x1021; + } else { + wCRC <<= 1; + } + } + } + + return my_htons(wCRC); +} + +// void WaitASecond() +//{ +//#ifdef ARDUINO +// delay(1000); +//#elif defined(WIN32) +// Sleep(1000); +//#else // +// usleep(1000000); +//#endif // ARDUINO +//} + +#ifndef ARDUINO +#ifdef WIN32 +#define MyMillis GetTickCount +#else // WIN32 + +/** \ingroup xmodem_internal + * \brief Return internal 'milliseconds' value for timing purposes + * + * \return A calculated 'milliseconds' value as an unsigned long integer + * + * This function returns the 'unsigned long' integer value for elapsed time + *based + * on the result of the 'gettimeofday()' API function. On 32-bit and Windows + *systems + * the value might wrap around, so you should be careful with your time + *comparisons (see the + * code _I_ wrote for the right way to do it). On 64-bit POSIX systems, this + *value will + * always increase.\n + * NOTE: Win32 defines this as a macro (see above) for the 'GetTickCount()' + *api, which + * returns a 32-bit value. POSIX x86 returns 32-bit, x64 returns + *64-bit. YMMV. +**/ +unsigned long MyMillis(void) { + struct timeval tv; + + gettimeofday(&tv, NULL); // 2nd parameter is obsolete anyway + + // NOTE: this won't roll over the way 'GetTickCount' does in WIN32 so I'll + // truncate it + // down to a 32-bit value to make it happen. Everything that uses + // 'MyGetTickCount' + // must handle this rollover properly using 'int' and not 'long' (or + // cast afterwards) + return ((unsigned int)((unsigned long)tv.tv_sec * 1000L + + (unsigned long)tv.tv_usec / 1000L)); +} +#endif // WIN32 +#endif // ARDUINO + +// Function GenerateSEQ (wSeq%) As String +// +// GenerateSEQ = Chr$(wSeq%) + Chr$(Not (wSeq%) And &HFF) +// +// End Function + +/** \ingroup xmodem_internal + * \brief Generate a sequence number pair, place into XMODEM_BUF + * + * \param pBuf A pointer to an XMODEM_BUF structure + * \param bSeq An unsigned char, typically cast from an unsigned long 'block + *number' + * + * This function generates the sequence pair for the XMODEM packet. The 'block + *number' + * is initially assigned a value of '1', and increases by 1 for each successful + *packet. + * That value is 'truncated' to a single byte and assigned as a sequence number + *for the + * packet itself. +**/ +void GenerateSEQ(XMODEM_BUF* pBuf, unsigned char bSeq) { + pBuf->aSEQ = bSeq; + pBuf->aNotSEQ = ~bSeq; +} + +/** \ingroup xmodem_internal + * \brief Generate a sequence number pair, place into XMODEMC_BUF (the CRC + *version) + * + * \param pBuf A pointer to an XMODEM_BUF structure + * \param bSeq An unsigned char, typically cast from an unsigned long 'block + *number' + * + * This function generates the sequence pair for the XMODEM packet. The 'block + *number' + * is initially assigned a value of '1', and increases by 1 for each successful + *packet. + * That value is 'truncated' to a single byte and assigned as a sequence number + *for the + * packet itself. +**/ +void GenerateSEQC(XMODEMC_BUF* pBuf, unsigned char bSeq) { + pBuf->aSEQ = bSeq; + pBuf->aNotSEQ = + (255 - bSeq); //~bSeq; these should be the same but for now I do this... +} + +/** \ingroup xmodem_internal + * \brief Get an XMODEM block from the serial device + * + * \param ser A 'SERIAL_TYPE' identifier for the serial connection + * \param pBuf A pointer to the buffer that receives the data + * \param cbSize The number of bytes/chars to read + * \return The number of bytes/chars read, 0 if timed out (no data), < 0 on + *error + * + * Call this function to read data from the serial port, specifying the number + *of + * bytes to read. This function times out after no data transferred (silence) + *for + * a period of 'SILENCE_TIMEOUT' milliseconds. This allows spurious data + *transfers + * to continue as long as there is LESS THAN 'SILENCE_TIMEOUT' between bytes, + *and + * also allows VERY SLOW BAUD RATES (as needed). However, if the transfer + *takes longer + * than '10 times SILENCE_TIMEOUT', the function will return the total number + *of bytes + * that were received within that time.\n + * The default value of 5 seconds, extended to 50 seconds, allows a worst-case + *baud + * rate of about 20. This should not pose a problem. If it does, edit the + *code. +**/ +short GetXmodemBlock(SERIAL_TYPE ser, char* pBuf, short cbSize) { + unsigned long ulCur; + short cb1; +// ** This function obtains a buffer of 'cbSize' bytes, ** +// ** waiting a maximum of 5 seconds (of silence) to get it. ** +// ** It returns the data within 'pBuf', returning the actual ** +// ** number of bytes transferred. ** + +#ifdef ARDUINO + char* p1; + short i1; + + p1 = pBuf; + cb1 = 0; + + ulCur = millis(); + ser->setTimeout(SILENCE_TIMEOUT); // 5 seconds [of silence] + + for (i1 = 0; i1 < cbSize; i1++) { + if (ser->readBytes(p1, 1) != + 1) // 5 seconds of "silence" is what fails this + { + break; + } + + cb1++; + p1++; + + if ((millis() - ulCur) > + (unsigned long)(10L * SILENCE_TIMEOUT)) // 10 times SILENCE TIMEOUT for + // TOTAL TIMEOUT + { + break; // took too long, I'm going now + } + } + +#elif defined(SFTARDCAL) +#ifndef WIN32 + struct pollfd aFD[2]; +#endif // WIN32 + int i1; + unsigned long ulStart; + + ulStart = ulCur = MyMillis(); + + cb1 = 0; + + do { +#ifndef WIN32 + aFD[0].fd = ser; + aFD[0].events = POLLIN | POLLERR; + aFD[0].revents = 0; + + i1 = poll(aFD, 1, 100); + if (!i1) { + continue; + } + + if (i1 < 0) { + fprintf(stderr, "poll error %d\n", errno); + return -1; + } +#endif // WIN32 + +#ifdef WIN32 + if (my_pollin(ser) > 0) +#else // WIN32 + if (aFD[0].revents & POLLIN) +#endif // WIN32 + { + i1 = my_read(ser, pBuf + cb1, cbSize - cb1); + + if (i1 > 0) { + cb1 += i1; + ulCur = MyMillis(); + } + } else { + MySleep(1); + } + } while (!QuitFlag() && cb1 < cbSize && + (MyMillis() - ulCur) < SILENCE_TIMEOUT && + (MyMillis() - ulStart) < (unsigned long)10L * SILENCE_TIMEOUT); + +#elif defined(WIN32) + +#error no win32 code yet + +#else // POSIX + unsigned long ulStart; + char* p1; + int i1, i2; + + if (fcntl(ser, F_SETFL, O_NONBLOCK) == -1) { + static int iFailFlag = 0; + + if (!iFailFlag) { + fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n", + errno); + fflush(stderr); + iFailFlag = 1; + } + } + + p1 = pBuf; + cb1 = 0; + + ulStart = ulCur = MyMillis(); + + for (i1 = 0; i1 < cbSize; i1++) { + while ((i2 = read(ser, p1, 1)) != 1) { + if (i2 < 0 && errno != EAGAIN) { + // read error - exit now + // return cb1; // how many bytes I actually read + goto the_end; + } else { + usleep(1000); // 1 msec + + if ((MyMillis() - ulCur) > SILENCE_TIMEOUT || // too much silence? + (MyMillis() - ulStart) > + 10 * SILENCE_TIMEOUT) // too long for transfer + { + // return cb1; // finished (return how many bytes I actually + // read) + goto the_end; + } + } + } + + // here it succeeds + + cb1++; + p1++; + + if ((MyMillis() - ulStart) > + 10 * SILENCE_TIMEOUT) // 10 times SILENCE TIMEOUT for TOTAL TIMEOUT + { + break; // took too long, I'm going now + } + } + +the_end: + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "GetXmodemBlock - request %d, read %d errno=%d\n", cbSize, + cb1, errno); + fflush(stderr); + //#ifdef DEBUG_CODE + debug_dump_buffer(-1, pBuf, cb1); +//#endif // DEBUG_CODE +#endif // STAND_ALONE + +#endif // ARDUINO + + return cb1; // what I actually read +} + +/** \ingroup xmodem_internal + * \brief Write a single character to the serial device + * + * \param ser A 'SERIAL_TYPE' identifier for the serial connection + * \param bVal The byte to send + * \return The number of bytes/chars written, or < 0 on error + * + * Call this function to write one byte of data to the serial port. Typically + * this is used to send things like an ACK or NAK byte. +**/ +int WriteXmodemChar(SERIAL_TYPE ser, unsigned char bVal) { + int iRval; +#ifdef ARDUINO + + iRval = ser->write(bVal); +// ser->flush(); // force sending it + +#elif defined(SFTARDCAL) + char buf[2]; // use size of '2' to avoid warnings about array size of '1' + + buf[0] = (char)bVal; + return (short)my_write(ser, buf, 1); + +#elif defined(WIN32) + +#error no win32 code yet + +#else // POSIX + char buf[2]; // use size of '2' to avoid warnings about array size of '1' + + if (fcntl(ser, F_SETFL, 0) == -1) // set blocking mode + { + static int iFailFlag = 0; + + if (!iFailFlag) { + fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n", + errno); + iFailFlag = 1; + } + } + + buf[0] = bVal; // in case args are passed by register + + iRval = write(ser, buf, 1); + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "WriteXmodemChar - returns %d\n", iRval); + if (iRval > 0) { + debug_dump_buffer(1, buf, 1); + } +#endif // STAND_ALONE, DEBUG_CODE +#endif // ARDUINO + + return iRval; +} + +/** \ingroup xmodem_internal + * \brief Send an XMODEM block via the serial device + * + * \param ser A 'SERIAL_TYPE' identifier for the serial connection + * \param pBuf A pointer to the buffer that receives the data + * \param cbSize The number of bytes/chars to write + * \return The number of bytes/chars written, < 0 on error + * + * Call this function to write data via the serial port, specifying the number + *of + * bytes to write. +**/ +int WriteXmodemBlock(SERIAL_TYPE ser, const void* pBuf, int cbSize) { + int iRval; +#ifdef ARDUINO + + iRval = ser->write((const uint8_t*)pBuf, cbSize); +// ser->flush(); // force sending it before returning + +#elif defined(SFTARDCAL) + + return (short)my_write(ser, pBuf, cbSize); + +#elif defined(WIN32) + +#error no win32 code yet + +#else // POSIX + + if (fcntl(ser, F_SETFL, 0) == -1) // set blocking mode + { + static int iFailFlag = 0; + + if (!iFailFlag) { + fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n", + errno); + fflush(stderr); + iFailFlag = 1; + } + } + + iRval = write(ser, pBuf, cbSize); + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "\r\nWriteXmodemBlock - returns %d\n", iRval); + fflush(stderr); + + if (iRval > 0) { + debug_dump_buffer(1, pBuf, cbSize); + } +#endif // STAND_ALONE, DEBUG_CODE +#endif + + return iRval; +} + +/** \ingroup xmodem_internal + * \brief Read all input from the serial port until there is 1 second of + *'silence' + * + * \param ser A 'SERIAL_TYPE' identifier for the serial connection + * + * Call this function to read ALL data from the serial port, until there is a + *period + * with no data (i.e. 'silence') for 1 second. At that point the function will + *return.\n + * Some operations require that any bad data be flushed out of the input to + *prevent + * synchronization problems. By using '1 second of silence' it forces + *re-synchronization + * to occur in one shot, with the possible exception of VERY noisy lines. The + *down side + * is that it may slow down transfers with a high data rate. +**/ +void XModemFlushInput(SERIAL_TYPE ser) { +#ifdef ARDUINO + unsigned long ulStart; + + ulStart = millis(); + + do { + if (ser->available()) { + ser->read(); // don't care about the data + ulStart = millis(); // reset time + } else { + delay(1); + } + + } while ((millis() - ulStart) < 1000); + +#elif defined(SFTARDCAL) + + my_flush(ser); + +#elif defined(WIN32) + +#error no win32 code yet + +#else // POSIX + unsigned long ulStart; + int i1; +#ifdef DEBUG_CODE + unsigned char buf[16]; + int cbBuf; +#else // DEBUG_CODE + unsigned char buf[2]; +#endif // DEBUG_CODE + + if (fcntl(ser, F_SETFL, O_NONBLOCK) == -1) { + static int iFailFlag = 0; + + if (!iFailFlag) { + fprintf(stderr, "Warning: 'fcntl(O_NONBLOCK)' failed, errno = %d\n", + errno); + iFailFlag = 1; + } + } + + ulStart = MyMillis(); +#ifdef DEBUG_CODE + cbBuf = 0; +#endif // DEBUG_CODE + while ((MyMillis() - ulStart) < 1000) { +#ifdef DEBUG_CODE + i1 = read(ser, &(buf[cbBuf]), 1); +#else // DEBUG_CODE + i1 = read(ser, buf, 1); +#endif // DEBUG_CODE + if (i1 == 1) { +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + cbBuf++; + if (cbBuf >= sizeof(buf)) { + debug_dump_buffer(-1, buf, cbBuf); + cbBuf = 0; + } +#endif // STAND_ALONE, DEBUG_CODE + ulStart = MyMillis(); + } else { + usleep(1000); + } + } + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + if (cbBuf > 0) { + debug_dump_buffer(-1, buf, cbBuf); + } +#endif // STAND_ALONE, DEBUG_CODE + +#endif // ARDUINO +} + +/** \ingroup xmodem_internal + * \brief Terminate the XMODEM connection + * + * \param pX A pointer to the 'XMODEM' object identifying the transfer + * + * Call this function prior to ending the XMODEM transfer. Currently the only + * thing it does is flush the input. +**/ +void XmodemTerminate(XMODEM* pX) { + XModemFlushInput(pX->ser); + + // TODO: close files? +} + +/** \ingroup xmodem_internal + * \brief Validate the sequence number of a received XMODEM block + * + * \param pX A pointer to an 'XMODEM_BUF' + * \param bSeq The expected sequence number (block & 255) + * \return A zero value on success, non-zero otherwise + * + * Call this function to validate a packet's sequence number against the block + *number +**/ +short ValidateSEQ(XMODEM_BUF* pX, unsigned char bSeq) { + return pX->aSEQ != 255 - pX->aNotSEQ || // ~(pX->aNotSEQ) || + pX->aSEQ != bSeq; // returns TRUE if not valid +} + +/** \ingroup xmodem_internal + * \brief Validate the sequence number of a received XMODEM block (CRC version) + * + * \param pX A pointer to an 'XMODEMC_BUF' + * \param bSeq The expected sequence number (block & 255) + * \return A zero value on success, non-zero otherwise + * + * Call this function to validate a packet's sequence number against the block + *number +**/ +short ValidateSEQC(XMODEMC_BUF* pX, unsigned char bSeq) { + return pX->aSEQ != 255 - pX->aNotSEQ || // ~(pX->aNotSEQ) || + pX->aSEQ != bSeq; // returns TRUE if not valid +} + +/** \ingroup xmodem_internal + * \brief Generic function to receive a file via XMODEM (CRC or Checksum) + * + * \param pX A pointer to an 'XMODEM_BUF' with valid bCRC, ser, and file + *members + * \return A zero value on success, negative on error, positive on cancel + * + * The calling function will need to poll for an SOH from the server using 'C' + *and 'NAK' + * characters (as appropriate) until an SOH is received. That value must be + *assigned + * to the 'buf' union (as appropriate), and the bCRC member assigned to + *non-zero if + * the server responded to 'C', or zero if it responded to 'NAK'. With the + *bCRC, + * ser, and file members correctly assigned, call THIS function to receive + *content + * via XMODEM and write it to 'file'.\n + * This function will return zero on success, a negative value on error, and a + *positive + * value if the transfer was canceled by the server. +**/ +int ReceiveXmodem(XMODEM* pX) { +#ifdef WIN32 + DWORD cbWrote; +#endif // WIN32 + int ecount, ec2; + long etotal, filesize, block; + unsigned char cY; // the char to send in response to a packet +// NOTE: to allow debugging the CAUSE of an xmodem block's failure, i1, i2, and +// i3 +// are assigned to function return values and reported in error messages. +#ifdef DEBUG_CODE + short i1, i2, i3; +#define DEBUG_I1 i1 = +#define DEBUG_I2 i2 = +#define DEBUG_I3 i3 = +#else // DEBUG_CODE +#define DEBUG_I1 /*normally does nothing*/ +#define DEBUG_I2 /*normally does nothing*/ +#define DEBUG_I3 /*normally does nothing*/ +#endif // DEBUG_CODE + + ecount = 0; + etotal = 0; + filesize = 0; + block = 1; + + // ** already got the first 'SOH' character on entry to this function ** + + // Form2.Show 0 '** modeless show of form2 (CANSEND) ** + // Form2!Label1.FloodType = 0 + // Form2.Caption = "* XMODEM(Checksum) BINARY RECEIVE *" + // Form2!Label1.Caption = "Errors: 0 Bytes: 0" + + pX->buf.xbuf.cSOH = (char)1; // assumed already got this, put into buffer + + do { + if (!pX->bCRC && + ((DEBUG_I1 GetXmodemBlock(pX->ser, ((char*)&(pX->buf.xbuf)) + 1, + sizeof(pX->buf.xbuf) - 1)) != + sizeof(pX->buf.xbuf) - 1 || + (DEBUG_I2 ValidateSEQ(&(pX->buf.xbuf), block & 255)) || + (DEBUG_I3 CalcCheckSum(pX->buf.xbuf.aDataBuf, + sizeof(pX->buf.xbuf.aDataBuf)) != + pX->buf.xbuf.bCheckSum))) { +// did not receive properly +// TODO: deal with repeated packet, sequence number for previous packet + +#ifdef DEBUG_CODE + sprintf(szERR, "A%ld,%d,%d,%d,%d,%d", block, i1, i2, i3, + pX->buf.xbuf.aSEQ, pX->buf.xbuf.aNotSEQ); +//#ifdef STAND_ALONE +// fprintf(stderr, "TEMPORARY (csum): seq=%x, ~seq=%x i1=%d, i2=%d, +// i3=%d\n", pX->buf.xbuf.aSEQ, pX->buf.xbuf.aNotSEQ, i1, i2, i3); +//#endif // STAND_ALONE +#endif // DEBUG_CODE + + XModemFlushInput(pX->ser); // necessary to avoid problems + + cY = _NAK_; // send NAK (to get the checksum version) + ecount++; // for this packet + etotal++; + } else if (pX->bCRC && + ((DEBUG_I1 GetXmodemBlock(pX->ser, ((char*)&(pX->buf.xcbuf)) + 1, + sizeof(pX->buf.xcbuf) - 1)) != + sizeof(pX->buf.xcbuf) - 1 || + (DEBUG_I2 ValidateSEQC(&(pX->buf.xcbuf), block & 255)) || + (DEBUG_I3 CalcCRC(pX->buf.xcbuf.aDataBuf, + sizeof(pX->buf.xbuf.aDataBuf)) != + pX->buf.xcbuf.wCRC))) { +// did not receive properly +// TODO: deal with repeated packet, sequence number for previous packet + +#ifdef DEBUG_CODE + sprintf(szERR, "B%ld,%d,%d,%d,%d,%d", block, i1, i2, i3, + pX->buf.xcbuf.aSEQ, pX->buf.xcbuf.aNotSEQ); +//#ifdef STAND_ALONE +// fprintf(stderr, "TEMPORARY (CRC): seq=%x, ~seq=%x i1=%d, i2=%d, +// i3=%d\n", pX->buf.xcbuf.aSEQ, pX->buf.xcbuf.aNotSEQ, i1, i2, i3); +//#endif // STAND_ALONE +#endif // DEBUG_CODE + + XModemFlushInput(pX->ser); // necessary to avoid problems + + if (block > 1) { + cY = _NAK_; // TODO do I need this? + } else { + cY = + 'C'; // send 'CRC' NAK (the character 'C') (to get the CRC version) + } + ecount++; // for this packet + etotal++; + } else { +#ifdef ARDUINO + if (pX->file.write((const uint8_t*)&(pX->buf.xbuf.aDataBuf), + sizeof(pX->buf.xbuf.aDataBuf)) != + sizeof(pX->buf.xbuf.aDataBuf)) { + return -2; // write error on output file + } +#elif defined(WIN32) + cbWrote = 0; + if (!WriteFile(pX->file, &(pX->buf.xbuf.aDataBuf), + sizeof(pX->buf.xbuf.aDataBuf), &cbWrote, NULL) || + cbWrote != sizeof(pX->buf.xbuf.aDataBuf)) { + XmodemTerminate(pX); + return -2; // write error on output file + } +#else // ARDUINO + if (write(pX->file, &(pX->buf.xbuf.aDataBuf), + sizeof(pX->buf.xbuf.aDataBuf)) != + sizeof(pX->buf.xbuf.aDataBuf)) { + XmodemTerminate(pX); + return -2; // write error on output file + } +#endif // ARDUINO + cY = _ACK_; // send ACK + block++; + filesize += sizeof(pX->buf.xbuf.aDataBuf); // TODO: need method to avoid + // extra crap at end of file + ecount = 0; // zero out error count for next packet + } + +#if defined(STAND_ALONE) || defined(SFTARDCAL) + fprintf(stderr, + "block %ld %ld bytes %d errors\r" +#ifndef SFTARDCAL + "\n" +#endif // SFTARDCAL + , + block - 1, filesize, ecount); +#endif // STAND_ALONE + + ec2 = 0; // ** error count #2 ** + + while (ecount < TOTAL_ERROR_COUNT && + ec2 < ACK_ERROR_COUNT) // ** loop to get SOH or EOT character ** + { + WriteXmodemChar(pX->ser, cY); // ** output appropriate command char ** + + if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) { + if (pX->buf.xbuf.cSOH == _CAN_) // ** CTRL-X 'CAN' - terminate + { + XmodemTerminate(pX); + return 1; // terminated + } else if (pX->buf.xbuf.cSOH == _EOT_) // ** EOT - end + { + WriteXmodemChar( + pX->ser, + _ACK_); // ** send an ACK (most XMODEM protocols expect THIS) + // WriteXmodemChar(pX->ser, _ENQ_); // ** send an ENQ + + return 0; // I am done + } else if (pX->buf.xbuf.cSOH == _SOH_) // ** SOH - sending next packet + { + break; // leave this loop + } else { + // TODO: deal with repeated packet, i.e. previous sequence number + + XModemFlushInput(pX->ser); // necessary to avoid problems (since the + // character was unexpected) + // if I was asking for the next block, and got an unexpected + // character, do a NAK; otherwise, + // just repeat what I did last time + + if (cY == _ACK_) // ACK + { + cY = _NAK_; // NACK + } + + ec2++; + } + } else { + ecount++; // increase total error count, and try writing the 'ACK' or + // 'NACK' again + } + } + + if (ec2 >= ACK_ERROR_COUNT) // wasn't able to get a packet + { + break; + } + + } while (ecount < TOTAL_ERROR_COUNT); + + XmodemTerminate(pX); + return 1; // terminated +} + +/** \ingroup xmodem_internal + * \brief Generic function to send a file via XMODEM (CRC or Checksum) + * + * \param pX A pointer to an 'XMODEM_BUF' with valid ser, and file members, and + *the polled + * 'NAK' value assigned to the cSOH member (first byte) within the 'buf' union. + * \return A zero value on success, negative on error, positive on cancel + * + * The calling function will need to poll for a 'C' or NAK from the client (as + *appropriate) + * and assign that character to the cSOH member in the 'buf' union (either xbuf + *or xcbuf since + * the 'cSOH' will always be the first byte). Then call this function to send + *content + * via XMODEM from 'file'.\n + * It is important to record the NAK character before calling this function + *since the 'C' or + * 'NAK' value will be used to determine whether to use CRC or CHECKSUM.\n + * This function will return zero on success, a negative value on error, and a + *positive + * value if the transfer was canceled by the receiver. +**/ +int SendXmodem(XMODEM* pX) { +#ifdef WIN32 + DWORD cbRead; +#endif // WIN32 + int ecount, ec2; + short i1; + long etotal, filesize, filepos, block; + + ecount = 0; + etotal = 0; + filesize = 0; + filepos = 0; + block = 1; + + pX->bCRC = + 0; // MUST ASSIGN TO ZERO FIRST or XMODEM-CHECKSUM may not work properly + +// ** already got first 'NAK' character on entry as pX->buf.xbuf.cSOH ** + +#ifdef ARDUINO + + filesize = pX->file.size(); + +#else // ARDUINO + +#ifdef WIN32 + filesize = (long)SetFilePointer(pX->file, 0, NULL, FILE_END); +#else // WIN32 + filesize = (long)lseek(pX->file, 0, SEEK_END); +#endif // WIN32 + if (filesize < 0) // not allowed + { +#ifdef STAND_ALONE + fputs("SendXmodem fail (file size)\n", stderr); +#endif // STAND_ALONE + return -1; + } + +#ifdef WIN32 + SetFilePointer(pX->file, 0, NULL, FILE_BEGIN); +#else // WIN32 + lseek(pX->file, 0, SEEK_SET); // position at beginning +#endif // WIN32 + +#endif // ARDUINO + + do { + // ** depending on type of transfer, place the packet + // ** into pX->buf with all fields appropriately filled. + + if (filepos >= filesize) // end of transfer + { + for (i1 = 0; i1 < 8; i1++) { + WriteXmodemChar(pX->ser, + _EOT_); // ** send an EOT marking end of transfer + + if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) != + 1) // this takes up to 5 seconds + { + // nothing returned - try again? + // break; // for now I loop, uncomment to bail out + } else if (pX->buf.xbuf.cSOH == + _ENQ_ // an 'ENQ' (apparently some expect this) + || pX->buf.xbuf.cSOH == _ACK_ // an 'ACK' (most XMODEM + // implementations expect + // this) + || pX->buf.xbuf.cSOH == _CAN_) // CTRL-X = TERMINATE + { + // both normal and 'abnormal' termination. + break; + } + } + + XmodemTerminate(pX); + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "SendXmodem return %d\n", i1 >= 8 ? 1 : 0); +#endif // STAND_ALONE + return i1 >= 8 ? 1 : 0; // return 1 if receiver choked on the 'EOT' + // marker, else 0 for 'success' + } + +// TODO: progress indicator [can be LCD for arduino, blinky lights, ??? and +// of course stderr for everyone else] +// If filesize& <> 0 Then Form2!Label1.FloodPercent = 100 * filepos& / +// filesize& + +#if defined(STAND_ALONE) || defined(SFTARDCAL) + fprintf(stderr, + "block %ld %ld of %ld bytes %d errors\r" +#ifndef SFTARDCAL + "\n" +#endif // SFTARDCAL + , + block, filepos, filesize, ecount); +#endif // STAND_ALONE + + if (pX->buf.xbuf.cSOH != 'C' // XMODEM CRC + && pX->buf.xbuf.cSOH != (char)_NAK_) // NAK + { + // increase error count, bail if it's too much + + ec2++; + } + +#ifdef ARDUINO + pX->file.seek(filepos); // in case I'm doing a 'retry' and I have to + // re-read part of the file +#elif defined(WIN32) + SetFilePointer(pX->file, filepos, NULL, FILE_BEGIN); +#else // ARDUINO + lseek(pX->file, filepos, SEEK_SET); // same reason as above +#endif // ARDUINO + + // fortunately, xbuf and xcbuf are the same through the end of 'aDataBuf' so + // I can read the file NOW using 'xbuf' for both CRC and CHECKSUM versions + + if ((filesize - filepos) >= sizeof(pX->buf.xbuf.aDataBuf)) { +#ifdef ARDUINO + i1 = pX->file.read(pX->buf.xbuf.aDataBuf, sizeof(pX->buf.xcbuf.aDataBuf)); +#elif defined(WIN32) + cbRead = 0; + if (!ReadFile(pX->file, pX->buf.xbuf.aDataBuf, + sizeof(pX->buf.xbuf.aDataBuf), &cbRead, NULL)) { + i1 = -1; + } else { + i1 = (int)cbRead; + } +#else // ARDUINO + i1 = + read(pX->file, pX->buf.xbuf.aDataBuf, sizeof(pX->buf.xcbuf.aDataBuf)); +#endif // ARDUINO + + if (i1 != sizeof(pX->buf.xcbuf.aDataBuf)) { + // TODO: read error - send a ctrl+x ? + } + } else { + memset(pX->buf.xcbuf.aDataBuf, '\x1a', + sizeof(pX->buf.xcbuf.aDataBuf)); // fill with ctrl+z which is what + // the spec says +#ifdef ARDUINO + i1 = pX->file.read(pX->buf.xbuf.aDataBuf, filesize - filepos); +#elif defined(WIN32) + cbRead = 0; + if (!ReadFile(pX->file, pX->buf.xbuf.aDataBuf, filesize - filepos, + &cbRead, NULL)) { + i1 = -1; + } else { + i1 = (int)cbRead; + } +#else // ARDUINO + i1 = read(pX->file, pX->buf.xbuf.aDataBuf, filesize - filepos); +#endif // ARDUINO + + if (i1 != (filesize - filepos)) { + // TODO: read error - send a ctrl+x ? + } + } + + if (pX->buf.xbuf.cSOH == + 'C' || // XMODEM CRC 'NAK' (first time only, typically) + ((pX->buf.xbuf.cSOH == _ACK_ || pX->buf.xbuf.cSOH == _NAK_) && + pX->bCRC)) // identifies ACK/NACK with XMODEM CRC + { + pX->bCRC = 1; // make sure (only matters the first time, really) + + // calculate the CRC, assign to the packet, and then send it + + pX->buf.xcbuf.cSOH = 1; // must send SOH as 1st char + pX->buf.xcbuf.wCRC = + CalcCRC(pX->buf.xcbuf.aDataBuf, sizeof(pX->buf.xcbuf.aDataBuf)); + + GenerateSEQC(&(pX->buf.xcbuf), (unsigned char)block); + + // send it + + i1 = WriteXmodemBlock(pX->ser, &(pX->buf.xcbuf), sizeof(pX->buf.xcbuf)); + if (i1 != sizeof(pX->buf.xcbuf)) // write error + { + // TODO: handle write error (send ctrl+X ?) + } + } else if (pX->buf.xbuf.cSOH == _NAK_ || // 'NAK' (checksum method, may + // also be with CRC method) + (pX->buf.xbuf.cSOH == _ACK_ && + !pX->bCRC)) // identifies ACK with XMODEM CHECKSUM + { + pX->bCRC = 0; // make sure (this ALSO allows me to switch modes on error) + + // calculate the CHECKSUM, assign to the packet, and then send it + + pX->buf.xbuf.cSOH = 1; // must send SOH as 1st char + pX->buf.xbuf.bCheckSum = + CalcCheckSum(pX->buf.xbuf.aDataBuf, sizeof(pX->buf.xbuf.aDataBuf)); + + GenerateSEQ(&(pX->buf.xbuf), (unsigned char)block); + + // send it + + i1 = WriteXmodemBlock(pX->ser, &(pX->buf.xbuf), sizeof(pX->buf.xbuf)); + if (i1 != sizeof(pX->buf.xbuf)) // write error + { + // TODO: handle write error (send ctrl+X ?) + } + } + + ec2 = 0; + + while (ecount < TOTAL_ERROR_COUNT && + ec2 < ACK_ERROR_COUNT) // loop to get ACK or NACK + { + if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) { + if (pX->buf.xbuf.cSOH == _CAN_) // ** CTRL-X - terminate + { + XmodemTerminate(pX); + + return 1; // terminated + } else if (pX->buf.xbuf.cSOH == _NAK_ || // ** NACK + pX->buf.xbuf.cSOH == 'C') // ** CRC NACK + { + break; // exit inner loop and re-send packet + } else if (pX->buf.xbuf.cSOH == _ACK_) // ** ACK - sending next packet + { + filepos += sizeof(pX->buf.xbuf.aDataBuf); + block++; // increment file position and block count + + break; // leave inner loop, send NEXT packet + } else { + XModemFlushInput(pX->ser); // for now, do this here too + ec2++; + } + } else { + ecount++; // increase total error count, then loop back and re-send + // packet + break; + } + } + + if (ec2 >= ACK_ERROR_COUNT) { + break; // that's it, I'm done with this + } + + } while ( + ecount < + TOTAL_ERROR_COUNT /* * 2 */); // twice error count allowed for sending + + // TODO: progress indicator + // If filesize& <> 0 And filepos& <= filesize& Then + // Form2!Label1.FloodPercent = 100 * filepos& / filesize& + // Else + // Form2!Label1.FloodPercent = 100 + // End If + + // ** at this point it is important to indicate the errors + // ** and flush all buffers, and terminate process! + + XmodemTerminate(pX); +#ifdef STAND_ALONE + fputs("SendXmodem fail (total error count)\n", stderr); +#endif // STAND_ALONE + return -2; // exit on error +} + +/** \ingroup xmodem_internal + * \brief Calling function for ReceiveXmodem + * + * \param pX A pointer to an 'XMODEM_BUF' with valid ser, and file members + * \return A zero value on success, negative on error, positive on cancel + * + * This is a generic 'calling function' for ReceiveXmodem that checks for + * a response to 'C' and 'NAK' characters, and sets up the XMODEM transfer + * for either CRC or CHECKSUM mode.\n + * This function will return zero on success, a negative value on error, and a + *positive + * value if the transfer was canceled by the receiver. +**/ +int XReceiveSub(XMODEM* pX) { + int i1; + + // start with CRC mode [try 8 times to get CRC] + + pX->bCRC = 1; + + for (i1 = 0; i1 < 8; i1++) { + WriteXmodemChar(pX->ser, 'C'); // start with NAK for XMODEM CRC + + if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) { + if (pX->buf.xbuf.cSOH == _SOH_) // SOH - packet is on its way + { + return ReceiveXmodem(pX); + } else if (pX->buf.xbuf.cSOH == + _EOT_) // an EOT [blank file? allow this?] + { + return 0; // for now, do this + } else if (pX->buf.xbuf.cSOH == _CAN_) // cancel + { + return 1; // canceled + } + } + } + + pX->bCRC = 0; + + // try again, this time using XMODEM CHECKSUM + for (i1 = 0; i1 < 8; i1++) { + WriteXmodemChar(pX->ser, _NAK_); // switch to NAK for XMODEM Checksum + + if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) { + if (pX->buf.xbuf.cSOH == _SOH_) // SOH - packet is on its way + { + return ReceiveXmodem(pX); + } else if (pX->buf.xbuf.cSOH == + _EOT_) // an EOT [blank file? allow this?] + { + return 0; // for now, do this + } else if (pX->buf.xbuf.cSOH == _CAN_) // cancel + { + return 1; // canceled + } + } + } + + XmodemTerminate(pX); + + return -3; // fail +} + +/** \ingroup xmodem_internal + * \brief Calling function for SendXmodem + * + * \param pX A pointer to an 'XMODEM_BUF' with valid ser, and file members + * \return A zero value on success, negative on error, positive on cancel + * + * This is a generic 'calling function' for SendXmodem that checks for polls by + *the + * receiver, and places the 'NAK' or 'C' character into the 'buf' member of the + *XMODEM + * structure so that SendXmodem can use the correct method, either CRC or + *CHECKSUM mode.\n + * This function will return zero on success, a negative value on error, and a + *positive + * value if the transfer was canceled by the receiver. +**/ +int XSendSub(XMODEM* pX) { + unsigned long ulStart; + +// waiting up to 30 seconds for transfer to start. this is part of the spec? + +#ifdef ARDUINO + ulStart = millis(); +#else // ARDUINO + ulStart = MyMillis(); +#endif // ARDUINO + + do { + if (GetXmodemBlock(pX->ser, &(pX->buf.xbuf.cSOH), 1) == 1) { + if (pX->buf.xbuf.cSOH == 'C' || // XMODEM CRC + pX->buf.xbuf.cSOH == _NAK_) // NAK - XMODEM CHECKSUM + { +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "Got %d, continuing\n", pX->buf.xbuf.cSOH); +#endif // STAND_ALONE + return SendXmodem(pX); + } else if (pX->buf.xbuf.cSOH == _CAN_) // cancel + { +#ifdef STAND_ALONE + fputs("XSendSub fail (cancel)\n", stderr); +#endif // STAND_ALONE + return 1; // canceled + } + } + } +#ifdef ARDUINO + while ((short)(millis() - ulStart) < 30000); // 30 seconds +#else // ARDUINO + while ((int)(MyMillis() - ulStart) < 30000); +#endif // ARDUINO + + XmodemTerminate(pX); + +#ifdef STAND_ALONE + fputs("XSendSub fail (timeout)\n", stderr); +#endif // STAND_ALONE + return -3; // fail +} + +// typedef struct _XMODEM_ +//{ +// SERIAL_TYPE ser; +// FILE_TYPE file; +// +// union +// { +// XMODEM_BUF xbuf; +// XMODEMC_BUF xcbuf; +// } buf; // 133 bytes +// +// unsigned char bCRC; // non-zero for CRC, zero for checksum +// +//} __attribute__((__packed__)) XMODEM; + +#ifdef ARDUINO + +short XReceive(SDClass* pSD, HardwareSerial* pSer, const char* szFilename) { + short iRval; + XMODEM xx; + + memset(&xx, 0, sizeof(xx)); + + xx.ser = pSer; + + if (pSD->exists((char*)szFilename)) { + pSD->remove((char*)szFilename); + } + + xx.file = pSD->open((char*)szFilename, FILE_WRITE); + if (!xx.file) { + return -9; // can't create file + } + + iRval = XReceiveSub(&xx); + + xx.file.close(); + + if (iRval) { + WriteXmodemChar(pSer, _CAN_); // cancel (make sure) + + pSD->remove((char*)szFilename); // delete file on error + } + + return iRval; +} + +int XSend(SDClass* pSD, HardwareSerial* pSer, const char* szFilename) { + short iRval; + XMODEM xx; + + memset(&xx, 0, sizeof(xx)); + + xx.ser = pSer; + + xx.file = pSD->open(szFilename, FILE_READ); + if (!xx.file) { + return -9; // can't open file + } + + iRval = XSendSub(&xx); + + xx.file.close(); + + return iRval; +} + +#else // ARDUINO + +int XReceive(SERIAL_TYPE hSer, const char* szFilename, int nMode) { + int iRval; + XMODEM xx; +#if !defined(ARDUINO) && !defined(WIN32) + int iFlags; +#endif // !ARDUINO + +#ifdef DEBUG_CODE + szERR[0] = 0; +#endif // DEBUG_CODE + memset(&xx, 0, sizeof(xx)); + + xx.ser = hSer; + +#ifdef WIN32 + DeleteFile(szFilename); + + nMode = nMode; // to avoid unused parameter warnings + // TODO: translate 'nMode' into file attributes? for now ignore it + xx.file = CreateFile(szFilename, GENERIC_READ | GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + if (xx.file == INVALID_HANDLE_VALUE) +#else // WIN32 + unlink(szFilename); // make sure it does not exist, first + xx.file = open(szFilename, O_CREAT | O_TRUNC | O_WRONLY, nMode); + + if (xx.file == -1) // bad file handle on POSIX systems +#endif // WIN32 + { +#ifdef STAND_ALONE + fprintf(stderr, "XReceive fail \"%s\" errno=%d\n", szFilename, errno); +#endif // STAND_ALONE + return -9; // can't create file + } + +#if !defined(ARDUINO) && !defined(WIN32) + iFlags = fcntl(hSer, F_GETFL); +#endif // !ARDUINO + + iRval = XReceiveSub(&xx); + +#if !defined(ARDUINO) && !defined(WIN32) + if (iFlags == -1 || fcntl(hSer, F_SETFL, iFlags) == -1) { + fprintf(stderr, + "Warning: 'fcntl' call to restore flags failed, errno=%d\n", + errno); + } +#endif // !ARDUINO + +#ifdef WIN32 + CloseHandle(xx.file); +#else // WIN32 + close(xx.file); +#endif // WIN32 + + if (iRval) { +#ifdef WIN32 + DeleteFile(szFilename); +#else // WIN32 + unlink(szFilename); // delete file on error +#endif // WIN32 + } + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "XReceive returns %d\n", iRval); +#endif // STAND_ALONE + return iRval; +} + +int XSend(SERIAL_TYPE hSer, const char* szFilename) { + int iRval; + XMODEM xx; +#if !defined(ARDUINO) && !defined(WIN32) + int iFlags; +#endif // !ARDUINO + +#ifdef DEBUG_CODE + szERR[0] = 0; +#endif // DEBUG_CODE + memset(&xx, 0, sizeof(xx)); + + xx.ser = hSer; + +#ifdef WIN32 + xx.file = + CreateFile(szFilename, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL); + + if (xx.file == INVALID_HANDLE_VALUE) +#else // WIN32 + xx.file = open(szFilename, O_RDONLY, 0); + + if (xx.file == -1) // bad file handle on POSIX systems +#endif // WIN32 + { +#ifdef STAND_ALONE + fprintf(stderr, "XSend fail \"%s\" errno=%d\n", szFilename, errno); +#endif // STAND_ALONE + return -9; // can't open file + } + +#if !defined(ARDUINO) && !defined(WIN32) + iFlags = fcntl(hSer, F_GETFL); +#endif // !ARDUINO + + iRval = XSendSub(&xx); + +#if !defined(ARDUINO) && !defined(WIN32) + if (iFlags == -1 || fcntl(hSer, F_SETFL, iFlags) == -1) { + fprintf(stderr, + "Warning: 'fcntl' call to restore flags failed, errno=%d\n", + errno); + } +#endif // !ARDUINO + +#ifdef WIN32 + CloseHandle(xx.file); +#else // WIN32 + close(xx.file); +#endif // WIN32 + +#if defined(STAND_ALONE) && defined(DEBUG_CODE) + fprintf(stderr, "XSend returning %d\n", iRval); +#endif // STAND_ALONE + return iRval; +} + +#endif // ARDUINO + +#if defined(STAND_ALONE) && !defined(SFTARDCAL) + +static const char szSER[] = "/dev/ttyU0"; + +#include <termios.h> + +/** \ingroup xmodem_standalone + * \brief Terminal configuration (POSIX only) + * + * \param iFile The open file handle for the serial connection + * \param iBaud The baud rate for the connection + * \param iParity The parity, < 0 for even, > 0 for odd, 0 for none + * \param iBits The number of bits (5, 6, 7, 8) + * \param iStop The number of stop bits (1 or 2) + * + * This is a sample tty config function to CORRECTLY set up a serial connection + * to allow XMODEM transfer. The important details here are the use of the + * 'termios' structure and utility functions to DISABLE all of the things that + * would otherwise cause trouble, like CRLF translation, CTRL+C handling, etc. +**/ +void ttyconfig(int iFile, int iBaud, int iParity, int iBits, int iStop) { + int i1; + struct termios sIOS; + + i1 = fcntl(iFile, F_GETFL); + + i1 |= O_NONBLOCK; // i1 &= ~O_NONBLOCK); // turn OFF non-blocking? + + fcntl(iFile, F_SETFL, i1); + + if (!tcgetattr(iFile, &sIOS)) { + cfsetspeed(&sIOS, iBaud); + sIOS.c_cflag &= ~(CSIZE | PARENB | CS5 | CS6 | CS7 | CS8); + sIOS.c_cflag |= + iBits == 5 ? CS5 : iBits == 6 ? CS6 : iBits == 7 ? CS7 + : CS8; // 8 is default + if (iStop == 2) { + sIOS.c_cflag |= CSTOPB; + } else { + sIOS.c_cflag &= ~CSTOPB; + } + + sIOS.c_cflag &= + ~CRTSCTS; // hardware flow control _DISABLED_ (so I can do the reset) + sIOS.c_cflag |= CLOCAL; // ignore any modem status lines + + if (!iParity) { + sIOS.c_cflag &= ~(PARENB | PARODD); + } else if (iParity > 0) // odd + { + sIOS.c_cflag |= (PARENB | PARODD); + } else // even (negative) + { + sIOS.c_cflag &= PARODD; + sIOS.c_cflag |= PARENB; + } + + // sIOS.c_iflag |= IGNCR; // ignore CR + + // do not translate characters or xon/xoff and ignore break + sIOS.c_iflag &= ~(IGNBRK | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY | + IMAXBEL | ISTRIP); // turn these off + +#if defined(__FreeBSD__) + sIOS.c_oflag &= ~(OPOST | ONLCR | OCRNL | TABDLY | ONOEOT | ONOCR | + ONLRET); // FreeBSD version +#else // Linux? YMMV + sIOS.c_oflag &= ~(OPOST | ONLCR | OCRNL | TABDLY | ONOCR | + ONLRET); // turn these off too (see man termios) +#endif // FBSD vs Linux + +// make sure echoing is disabled and control chars aren't translated or omitted +#if defined(__FreeBSD__) + sIOS.c_lflag &= ~(ECHO | ECHOKE | ECHOE | ECHONL | ECHOPRT | ECHOCTL | + ICANON | IEXTEN | ISIG | ALTWERASE); +#else // Linux? YMMV + sIOS.c_lflag &= ~(ECHO | ECHOKE | ECHOE | ECHONL | ECHOPRT | ECHOCTL | + ICANON | IEXTEN | ISIG); +#endif // FBSD vs Linux + sIOS.c_cc[VMIN] = 0; // ensures no 'grouping' of input + sIOS.c_cc[VTIME] = 0; // immediate return + + if (tcsetattr(iFile, TCSANOW, &sIOS)) { + fprintf(stderr, "error %d setting attributes\n", errno); + } + } else { + fprintf(stderr, "error %d getting attributes\n", errno); + } +} + +/** \ingroup xmodem_standalone + * \brief Arduino 'reset' function + * + * \param iFile The open file handle for the serial connection + * + * The Arduino serial port typically has the DTR/RTS lines configured so that + * a proper 'pulse' will cause a hardware reset of the device. This function + *will + * send that pulse to the Arduino, and wait for a short time afterwards for the + * hardware reset to take place. +**/ +void reset_arduino(int iFile) { + unsigned int sFlags; + unsigned long ulStart; + int i1; + + // toggle the RTS and DTR high, low, then high - so much easier via + // POSIX-compatible OS! + + ioctl(iFile, TIOCMGET, &sFlags); + + sFlags &= ~(TIOCM_DTR | TIOCM_RTS); // the high to low transition discharges + // the capacitor (signal is inverted on + // board) + if (ioctl(iFile, TIOCMSET, &sFlags) < 0) { + fprintf(stderr, "WARNING: ioctl() returns < 0, errno=%d (%xH)\n", errno, + errno); + } + + usleep( + 250000); // avrdude does this for 50 msecs, my change has it at 50msecs + + sFlags |= TIOCM_DTR | TIOCM_RTS; // leave it in THIS state when I'm done + if (ioctl(iFile, TIOCMSET, &sFlags) < 0) { + fprintf(stderr, "WARNING: ioctl() returns < 0, errno=%d (%xH)\n", errno, + errno); + } + + usleep(50000); // avrdude does this for 50 msecs (no change) + + ulStart = MyMillis(); + + // flush whatever is there, (5 seconds) + + while ((MyMillis() - ulStart) < 5000) { + i1 = read(iFile, &i1, 1); + if (i1 == 1) { + ulStart = MyMillis(); + } else { + usleep(1000); + } + } +} + +int main(int argc, char* argv[]) { + int hSer; + char tbuf[256]; + int i1, iSR = 0; + + if (argc < 3) { + fputs("Usage: [prog] [S|R] filename\n", stderr); + return 1; + } + + if (argv[1][0] == 'R' || argv[1][1] == 'r') { + iSR = -1; + } else if (argv[1][0] == 'S' || argv[1][1] == 's') { + iSR = 1; + } else if (argv[1][0] == 'X' || argv[1][1] == 'x') { + iSR = 0; // test function + } else { + fputs("Usage: [prog] [S|R] filename (b)\n", stderr); + return 1; + } + + hSer = open(szSER, (O_RDWR | O_NONBLOCK), 0); + if (hSer == -1) { + fprintf(stderr, "Unable to open \"%s\" errno=%d\n", szSER, errno); + return 3; + } + + fputs("TTYCONFIG\n", stderr); + ttyconfig(hSer, 9600, 0, 8, 1); + + reset_arduino(hSer); + + fprintf(stderr, "Sleeping for 10 seconds to allow reset\n"); + + // usleep(10000000); + for (i1 = 0; i1 < 10; i1++) { + XModemFlushInput(hSer); + } + + for (i1 = 0; i1 < 3; i1++) { + sprintf(tbuf, "X%c%s", argv[1][0], argv[2]); + + fprintf(stderr, "writing: \"%s\"\n", tbuf); + strcat(tbuf, "\r"); + WriteXmodemBlock(hSer, tbuf, strlen(tbuf)); + + fputs("flush input\n", stderr); + XModemFlushInput(hSer); + + // wait for an LF response + + if (iSR > 0) { + fputs("XSEND\n", stderr); + if (XSend(hSer, argv[2])) { + fputs("ERROR\n", stderr); + } else { + fputs("SUCCESS!\n", stderr); + i1 = 0; + break; + } + } else if (iSR < 0) { + fputs("XRECEIVE\n", stderr); + if (XReceive(hSer, argv[2], 0664)) { + fputs("ERROR\n", stderr); + } else { + fputs("SUCCESS!\n", stderr); + i1 = 0; + break; + } + } else { + // test function + XModemFlushInput(hSer); // continue doing this + break; // done (once only) + } + } + + fputs("EXIT\n", stderr); + close(hSer); + + return i1 ? 1 : -1; +} +#endif // STAND_ALONE |
