|
25 Years of Programming
An open source source for C, C++, OWL, BASIC, MDB, XLS, DOT, and more... |
Home Projects Up Sitemap Search Blog Forum+Chat About Us Privacy Terms of Use Feedback FAQ Images Services Ads Donate Humor |
|
|
Borland C++ xmodem send/receive serial communications classSerialCom is a drop-in Borland C++ 4.0 class for MSDOS that provides RS-232 serial communication capabilities, including xmodem support in single-file or batch mode. It uses Borland's DOS-only serial communications functions. This page also has a modem program that uses the SerialCom class.Other versions: Windows conversion of this, as class SWinCommDev. There are also some other notes about RS232 and serial communications. |
|
/* serialco.cpp 11-6-99
Copyright (C)1996-99, 2006 Steven Whitney.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
Version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
------
Notes:
handles communications through a serial port with some functions for Hayes modem support,
and can now do xmodem send/receive in single file or batch mode.
1/25/06: In spite of how messy this looks, the smodem program that uses this class worked well at
transferring files in batch mode between my H89 (running Mex.com) and Gateway P5-90,
with occasional glitches. I don't remember what the specific glitches were, but they were
handshaking-related, not program crashes, and it was useful to be sitting at both computers to
restart the transfer. If either side is to operate unattended, this program probably needs
more work.
------
TO DO:
--See http://en.wikipedia.org/wiki/Cyclic_redundancy_check for CRC resources and links.
--Google [xmodem crc gnu] to find suitable GPL equivalent code for CRC calcs.
Also try Teoma, Clusty.
--copy any new fns here to wserial.cpp and modify them for Windows use there.
--Some of my old programs refer to functions no longer here. Resolve as they're encountered.
--if a FILE fails in xmodem batch receive mode, let sender believe you are still
receiving it (just don't write anything to disk), but be sure to warn USER
at end of transfer which file(s) failed to be received. Abandoning the entire batch
transfer is a needless waste.
--If I ever bring over interrupt driven routines, some methods (and maybe fns) from
wserial (Windows version) may be required here.
--operator << could be modified for use in a simple pgm that acts as a breakout box,
continually reading and reporting on status of both COM ports. update display
only if something changes. However, MSD already does report everything available that
is of any likely interest. (framing error, etc., and delta-DCD, etc. infrequently
needed).
------
NOTES:
--Is INT14 inherently too slow for high speed transfers?
--Anticipated uses:
1)Program sends output to H89 computer FOR TEXT STATUS REPORTS:
SerialCom H89;
H89 << string(statustext); // remember to include \r\n or whatever's needed
2)Program sends output to H100, WHICH SERVES AS A SECONDARY GRAPHICS DISPLAY:
int x, y, color;
SerialCom H100;
string outstring = tostring(x) + "," + tostring(y) + "," + tostring(color) + "\r";
H100 << outstring; // H100 sets its pixel at (x,y) to color.
*/
#include <bios.h>
#include <dos.h>
#pragma hdrstop
// This program should work as-is in checksum mode. To use CRC mode, I use old functions of
// unknown source that have no notice of authorship or copyright, so there is no one to ask
// for permission to use them, and thus I can't post them here. Those functions are
// available elsewhere on the internet, but it may take some searching.
// There are probably also equivalent routines licensed under the GNU GPL that could be used, but
// there are many CRC-calc routines and determining which one is "equivalent" may be difficult.
//
// crc.cpp is required for this program to calculate Cyclic Redundancy Check bytes.
// It has the following two functions:
// ushort crc_update(ushort& crc, char ch) // ushort = unsigned short
// ushort crc_finish(ushort& crc)
#define ALLOWCRC 0 // 1 if you provide your own CRC calculation routines
#if (ALLOWCRC)
#include "c:\bcs\library\crc.cpp"
#else
// dummy placeholder fns that will allow compilation, but won't actually work.
ushort crc_update(ushort& crc, char ch) { return 0; }
ushort crc_finish(ushort& crc) { return 0; }
#endif
#include "c:\bcs\library\filearay.cpp"
//////////////////////////////////////////////////////////////////////////////
#define RETRYMAX 20 // max retrys before abort
#define SECTORSIZE 128 // must match CPM value
#define BUFSEC 255 // sectors to buffer during XMODEM receipt (32k - 1 sector)
// (this is the max that ostream.write() can handle)
// we actually allocate 1 extra to avoid overflow.
#define TIMEOUT (256) // timeout character, safely above transmittable ASCII
//////////////////////////////////////////////////////////////////////////////
class SerialCom
{
public:
SerialCom(int c = 0, int b = 1200, int d = 8, int s = 1, int p = 0);
// functions:
// port control
BOOL InitComPort(int,int,int,int,int); // initialize com port
BOOL ResetComPort(); // reinit port with existing values
// status
friend ostream& operator << (ostream& os, const SerialCom&); // text status report
uint PortStatus() const; // get encoded status of serial port
string GetStatusText(uint status) const; // translate encoded status int
BOOL CharWaiting();
BOOL CarrierDetect();
int GetBaud() { return(baud); }
// receive
uint ReceiveChar(long secs); // wait maximum of n seconds for 1 char
string ReceiveString(); // build a string from incoming chars
uint FlushIncoming();
// send
uint SendChar(uchar ch); // send a char when port can accept it
void SendString(const string& s); // send string to modem
SerialCom& operator << (const string& s); // send string to modem
// file transfer
int XMODEMReceiveFiles(string& filename, BOOL batch = FALSE);
int XMODEMSendFiles(string& filename, BOOL batch = FALSE);
// Hayes modem control
void HayesReset(); // reset Hayes modem
void HayesCommand(const string& command); // issue Hayes modem command
void HayesResultwait(); // wait for Hayes result code
// variables
enum dispmodes { FULL, BRIEF } displaymode; // mode for status reports
// Hayes parameters we need to know:
BOOL HayesVerbose; // verbose result codes?
BOOL HayesEcho; // modem echoes input to it?
BOOL crcmode; // should be able to make this local to fns?
// once it works properly automatically, no need to ask user about it.
protected:
// functions
void CalcCRC(ushort& crc, uint ch);
uint ReadAndCalcCRC(ushort& crc, long secs);
void SendCRC(ushort crc);
BOOL UserCancel();
// variables
int port; // 0 = com1, 1 = com2...
int baud;
int databits;
int stopbits;
int parity;
BOOL xoff; // when TRUE, sending is disabled (not yet used)
};
//----------------------------------------------------------------------------
// constructor
// port parameter here corresponds to com1, com2, etc.
// default is com2, 1200 baud, 8 data, 1 stop, no parity
SerialCom::SerialCom(int c, int b, int d, int s, int p)
{
HayesVerbose = HayesEcho = TRUE;
displaymode = FULL;
#if (ALLOWCRC)
crcmode = TRUE; // CRC is better than checksum if it's available
#else
crcmode = FALSE;
#endif
xoff = FALSE;
// start with known legal values in case InitComPort() fails
port = 1; // com2
baud = 1200; // always usable, even for Hayes300 modem
databits = 8;
stopbits = 1;
parity = 0;
// first initialize with the legal values
InitComPort(port,baud,databits,stopbits,parity);
// then try to override with the provided ones
InitComPort(c,b,d,s,p);
} //constructor
//----------------------------------------------------------------------------
// initialize com port
// if any parameter is invalid, its value isn't changed.
// if a parameter is valid, its associated member is set to it.
BOOL SerialCom::InitComPort(int c, int b, int d, int s, int p)
{
BOOL ok = TRUE; // stays true only if all parameters are valid
if((c >= 0) && (c <= 1)) // only allow 0,1 for com1, com2
port = c;
else
{
cerr << "Invalid com port, ignored. 0 = com1, 1 = com2..." << endl;
ok = FALSE;
}
char ch = 0; // communications parameters built in this byte
switch(b)
{
case 1200: baud = b; ch |= _COM_1200; break; // Hayes 300
case 2400: baud = b; ch |= _COM_2400; break; // Hayes 2400, H100 Mex?
case 4800: baud = b; ch |= _COM_4800; break; // H89 Mex
case 9600: baud = b; ch |= _COM_9600; break;
default:
cerr << "Invalid baud rate, ignored. Use 1200, 2400, 4800, 9600." << endl;
ok = FALSE;
break;
}
switch(d)
{
case 7: databits = d; ch |= _COM_CHR7; break;
case 8: databits = d; ch |= _COM_CHR8; break;
default:
cerr << "Invalid data bits, ignored. Use 7 or 8." << endl;
ok = FALSE;
break;
}
switch(s)
{
case 1: stopbits = s; ch |= _COM_STOP1; break;
case 2: stopbits = s; ch |= _COM_STOP2; break;
default:
cerr << "Invalid stop bits, ignored. Use 1 or 2." << endl;
ok = FALSE;
break;
}
switch(p)
{
case 0: parity = p; ch |= _COM_NOPARITY; break;
case 1: parity = p; ch |= _COM_ODDPARITY; break;
case 2: parity = p; ch |= _COM_EVENPARITY; break;
default:
cerr << "Invalid parity, ignored. Use 0(None), 1(Odd), or 2(Even)." << endl;
ok = FALSE;
break;
}
_bios_serialcom(_COM_INIT, port, ch);
xoff = FALSE; // always reenable
return(ok);
} //InitComPort
//----------------------------------------------------------------------------
// reinit port with existing values (in hope of clearing error conditions, etc.)
BOOL SerialCom::ResetComPort()
{
return(InitComPort(port, baud, databits, stopbits, parity));
}
//----------------------------------------------------------------------------
// sends string to serial port. Returns reference to itself to allow chaining.
// You must convert non-string arguments to string before calling.
// Briefly tried a template version to accomodate any type, without success.
// Note that this is a member, not friend, and doesn't involve ostreams at all.
// #error, as member, order of arguments not significant?: must be global friend?
// Both these should be legal, given SerialCom sc:
// sc << string("Value = ") + tostring(value) + "\n"; // 1 long string
// sc << string("Value = ") << tostring(value) << string("\n"); // chained output
// This function not yet tested. For use with H89, H100.
SerialCom& SerialCom::operator << (const string& s)
{
SendString(s);
return(*this);
}
//----------------------------------------------------------------------------
// get status of the port
uint SerialCom::PortStatus() const
{
return(_bios_serialcom(_COM_STATUS,port,0));
} //PortStatus
//----------------------------------------------------------------------------
// returns TRUE if carrier detected
BOOL SerialCom::CarrierDetect()
{
char ch = 0;
return((_bios_serialcom(_COM_STATUS, 0, ch) & (1 << 7)) != 0);
}
//----------------------------------------------------------------------------
// translate provided status to text form.
// i should be the return value from a call to _bios_serialcom()
// port errors seem to be automatically CLEARED by a bioscom() status request,
// so you should use this instead of operator << if you want an existing error analyzed.
// in that situation, (when i is not the result of a call with parameter _COM_STATUS),
// the low byte is garbage, not a valid status word.
string SerialCom::GetStatusText(uint i) const
{
ostrstream os;
if(displaymode == FULL)
{
os << "Status:" << endl;
os << "Timeout Error : " << ((i & (1 << 15)) ? "YES" : "NO") << endl;
os << "Tx Shift Register Empty : " << ((i & (1 << 14)) ? "YES" : "NO") << endl;
os << "Tx Holding Register Empty : " << ((i & (1 << 13)) ? "YES" : "NO") << endl;
os << "Break Detected : " << ((i & (1 << 12)) ? "YES" : "NO") << endl;
os << "Framing Error : " << ((i & (1 << 11)) ? "YES" : "NO") << endl;
os << "Parity Error : " << ((i & (1 << 10)) ? "YES" : "NO") << endl;
os << "Overrun Error : " << ((i & (1 << 9)) ? "YES" : "NO") << endl;
os << "Data Ready : " << ((i & (1 << 8)) ? "YES" : "NO") << endl;
os << endl;
os << "Carrier Detect : " << ((i & (1 << 7)) ? "YES" : "NO") << endl;
os << "Ring Indicator : " << ((i & (1 << 6)) ? "YES" : "NO") << endl;
os << "Data Set Ready : " << ((i & (1 << 5)) ? "YES" : "NO") << endl;
os << "Clear To Send : " << ((i & (1 << 4)) ? "YES" : "NO") << endl;
os << "Carrier Detect Changed? : " << ((i & (1 << 3)) ? "YES" : "NO") << endl;
os << "Trailing Edge Ring Detector: " << ((i & (1 << 2)) ? "YES" : "NO") << endl;
os << "Data Set Ready Changed? : " << ((i & (1 << 1)) ? "YES" : "NO") << endl;
os << "Clear To Send Changed? : " << ((i & 1) ? "YES" : "NO") << endl;
}
else
{
os << "TE SRE HRE Brk FE PE OE Rdy DCD RI DSR CTS ^DCD ^RI ^DSR ^CTS";
os << endl;
os << ((i & (1 << 15)) ? "Y" : "N") << " ";
os << ((i & (1 << 14)) ? "Y" : "N") << " ";
os << ((i & (1 << 13)) ? "Y" : "N") << " ";
os << ((i & (1 << 12)) ? "Y" : "N") << " ";
os << ((i & (1 << 11)) ? "Y" : "N") << " ";
os << ((i & (1 << 10)) ? "Y" : "N") << " ";
os << ((i & (1 << 9)) ? "Y" : "N") << " ";
os << ((i & (1 << 8)) ? "Y" : "N") << " ";
os << ((i & (1 << 7)) ? "Y" : "N") << " ";
os << ((i & (1 << 6)) ? "Y" : "N") << " ";
os << ((i & (1 << 5)) ? "Y" : "N") << " ";
os << ((i & (1 << 4)) ? "Y" : "N") << " ";
os << ((i & (1 << 3)) ? "Y" : "N") << " ";
os << ((i & (1 << 2)) ? "Y" : "N") << " ";
os << ((i & (1 << 1)) ? "Y" : "N") << " ";
os << ((i & 1) ? "Y" : "N") << endl;
}
os << "-----------------------------------------------------------------------------" << endl;
os << ends;
string s = os.str();
delete[] os.str();
return(s);
} //GetStatusText
//----------------------------------------------------------------------------
// output to a stream, produces a complete status report
// in full-page or brief mode, depending on setting of member displaymode.
ostream& operator << (ostream& os, const SerialCom& s)
{
uint i = s.PortStatus();
// these members NOT obtained from the COM port
os << "Hayes: Verbose = " << (s.HayesVerbose ? "YES" : "NO");
os << " Echo = " << (s.HayesEcho ? "YES" : "NO") << endl;
os << "Port, Baud, Data, Stop, Parity = Com" << (s.port + 1) << ", " << s.baud;
os << ", " << s.databits << ", " << s.stopbits << ", " << s.parity << endl;
os << s.GetStatusText(i);
return(os);
} //operator <<
//----------------------------------------------------------------------------
// modem has char waiting. this is useful. keep it.
BOOL SerialCom::CharWaiting()
{
return((_bios_serialcom(_COM_STATUS, 0, char()) & (1 << 8)) != 0); // check data ready bit
} //CharWaiting
//----------------------------------------------------------------------------
// This is a key function. It tries to get a char from the serial port,
// and returns either the char received (0-255) or a value > 255 that indicates
// a failure code. Any return > 255 indicates a (hopefully) abnormal condition.
// It should be expanded as necessary to handle any situation and return any
// coded status value that ever proves useful.
// It must return a uint (not char) so it can return the status values:
// TIMEOUT = 256, ERROR = (uint)(ERR) = (uint)(-1) = 0xFFFF.
// See Help | bioscom() for status bits. The ones I test here as those that
// seem caused by *receive* errors are: 0000 1110 0000 0000 = 0xE00.
//
uint SerialCom::ReceiveChar(long secs)
{
if(!CharWaiting()) // don't spend time constructing the TTimes unless you have to
{
// remember the minimum TTime difference or increment is 1 sec.
TTime endtime; // initialize to now.
endtime += secs; // stop waiting n seconds in the future.
while(!CharWaiting()) // don't spend time constructing the TTime unless you have to
if(TTime() >= endtime) // >= allows ReceiveChar(0) to fail immediately if no char
return(TIMEOUT); // nothing arrived in time
}
uint ch = _bios_serialcom(_COM_RECEIVE, 0, char()); // get the input
// #error there have been transmission errors that 0xE00 didn't catch
if(ch & 0xE00) // if there was an error bit set,
// if(ch > 255) // this is more general, but catches everything, even nonerrors
{
// full report (slow)
// cout << GetStatusText(ch) << "Com receive error, as above." << endl;
// faster, but still slow
cout << "Receive Error (hex=): " << hex << ch << dec << endl;
// presskey();
// ResetComPort(); // enable if it seems ACE must be reset after serious errors
// (sometimes errors seem to cause hangup or loopback mode)
return((uint)ERR); // return generic error code
}
// an eventual possibility, but you probably can't do this here.
// in binary receptions, it would often erroneously turn off the ability to send ACK/NAK.
// if(ch == XOFF)
// xoff = TRUE;
// else
// if(ch == XON)
// xoff = FALSE;
return(ch & 0xFF); // else return the ascii char, stripped of all status bits
} //ReceiveChar
//----------------------------------------------------------------------------
// accumulate chars from the com port into a string
string SerialCom::ReceiveString()
{
string rbuf;
uint ch;
// while chars are available, append them to the string. it WILL append error chars!
while((ch = ReceiveChar(1)) != TIMEOUT)
rbuf += (char)ch;
return(rbuf);
} //ReceiveString
//----------------------------------------------------------------------------
// get and discard incoming characters until a 1 sec silence (or an error).
// returns # of chars flushed
uint SerialCom::FlushIncoming()
{
uint i = 0;
// both these methods assume (maybe wrongly) that we CANNOT get ahead of incoming chars.
// if we can, then it will exit the wait loop prematurely.
// while(CharWaiting())
// {
// // new method (no wait allowed, or only enough to execute the code):
// if(ReceiveChar(0) == TIMEOUT)
// break;
// else
// i++;
//
// // old method:
// // allowing 1 sec might hang here due to remote retries at 1 sec intervals?
// if(ReceiveChar(1) != TIMEOUT)
// i++;
// }
// you usually want to wait out a sector data block, for which this should be perfect.
while(ReceiveChar(1) != TIMEOUT) // wait for a 1 second silence
i++;
return(i);
} //FlushIncoming
//----------------------------------------------------------------------------
// This is a key function. It tries to send a char through the serial port,
// and returns either the char sent (0-255) or a value > 255 that indicates
// a failure code. Any return > 255 indicates a (hopefully) abnormal condition.
// It should be expanded as necessary to handle any situation and return any
// coded status value that ever proves useful.
// It must return an int (not char) so it can return the status values:
// TIMEOUT = 256, ERROR = (uint)(ERR) = (uint)(-1) = 0xFFFF.
// See Help | bioscom() for status bits. The ones I test here as those that
// seem caused by *send* errors are: 1000 0000 0000 0000 = 0x8000. (only timeout)
//
// you can test using: if(SendChar(ch) != ch)
//
// you would test xoff here, but it introduces many complications: you must build
// a backlog of unsent chars for when you CAN send.
//
// something else you could do here is test for WAITING chars; if any, read
// them and append to a buffer (or string). ReceiveChar would return chars from the
// buffer (until empty) before reading any new ones from the port. this would
// minimize lost chars during long periods where you are sending only.
uint SerialCom::SendChar(uchar ch)
{
// if you impose a time limit, it should probably go here.
// but Help|bioscom makes it seem that timeout is already handled by hardware,
// and appropriately reported in status bit. (maybe uses MSDOS mode setting?)
// wait until holding register empty
while( (_bios_serialcom(_COM_STATUS, 0, char()) & (1 << 13)) == 0);
uint i = _bios_serialcom(_COM_SEND, 0, ch); // get status
if(i & 0x8000) // if there was any error bit set,
{
// ResetComPort(); // enable if it seems ACE must be reset after serious errors
// (sometimes errors seem to cause hangup or loopback mode)
// if(i & 0x8000) // explicit test for timeout, if other tests added above.
return(TIMEOUT); // return timeout
// return((uint)ERR); // return value for other errors
}
return(ch); // if successful, return the original char
} // SendChar
//----------------------------------------------------------------------------
// send all chars from a string to modem.
void SerialCom::SendString(const string& s)
{
for(int i = 0 ; i < s.length() ; i++)
SendChar(s[i]);
} //SendString
//////////////////////////////////////////////////////////////////////////////
// crc functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// calculates crc or checksum, depending on which mode is active.
void SerialCom::CalcCRC(ushort& crc, uint ch)
{
if(crcmode)
crc_update(crc, ch);
else
crc = (crc + ch) & 0xff; // this chops it down to 1 char size: correct?
} //CalcCRC
//----------------------------------------------------------------------------
// read a char and tally it into checksum/CRC.
// returns the same as ReceiveChar, passed through.
uint SerialCom::ReadAndCalcCRC(ushort& crc, long secs)
{
uint ch = ReceiveChar(secs);
if(ch < 256) // only if received without error
CalcCRC(crc, ch);
return(ch);
} //ReadAndCalcCRC
//----------------------------------------------------------------------------
// Sends out crc or checksum, depending on which mode is active.
void SerialCom::SendCRC(ushort crc)
{
if(crcmode)
{
SendChar((crc >> 8) & 0xFF); // send CRC high byte
SendChar(crc & 0xFF); // send CRC low byte
}
else
SendChar(crc); // send checksum byte
} //SendCRC
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
// XMODEM transfer functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// returns TRUE if user has cancelled with CTLX
BOOL SerialCom::UserCancel()
{
if(csts())
if(ci() == CTLX) // if it's not, you've lost the keystroke
{
cout << "++ Transfer cancelled by you ++ " << endl;
return(TRUE);
}
return(FALSE);
} //UserCancel
//----------------------------------------------------------------------------
// gets one or more files from remote system.
// on entry, filename is the name of a file, or desired destination PATH if batch mode.
// returns the number of files successfully received.
int SerialCom::XMODEMReceiveFiles(string& filename, BOOL batch)
{
int filecount = 0; // number of files successfully received
uint moderequests = 0;
// sector chars buffer, with an extra sector, just in case
// below here, you must break to function end, or delete[] bigbuf at every return point
char* bigbuf = new char[(BUFSEC + 1) * SECTORSIZE];
string path; // desired destination PATH for received files.
if(batch && filename.length())
{
path = filename;
if(path.length() == 1) // drive only, turn "D" to "D:" and use default subdir
path += ':';
else // ensure longer path is terminated by a : or \
{
char ch = path[path.length() - 1];
if((path.length() > 2) && (ch != '\\')) // don't change "D:" to "D:\"
path += '\\';
}
}
ResetComPort(); // hope that it clears error conditions
// FlushIncoming(); // discard any incoming garbage
while(1) // while(there are more files to receive)
{
BOOL bad = FALSE; // if TRUE, whole procedure aborts.
BOOL done = FALSE; // whether various sections are done
//----------------------------------------------------------------------------
// if batch mode, receive filename. if not batch, we already have it.
if(batch)
{
cout << "Awaiting filename " << endl;
filename.remove(0); // fresh start
// I can only make the filename transfer work (with Mex) in checksum mode.
// Mex responds as though it supports crc mode for filename, but even in crc mode
// it only accepts 1 byte as a reply, so it really uses only checksum for filenames.
// My routines do support either mode.
// for dealing with Mex, crcmode is always FALSE inside here, but restored TRUE later
// for the data transfer.
crcmode = FALSE;
while(!bad && !done) // exits: (1)good filename or (2)too many errors
{
// every good char clears error count because the only important thing
// is whether there are so many consecutive errors that you must abort.
uint errors = 0; // error count
uint ch = ReceiveChar(1); // get filename character
switch(ch)
{
case TIMEOUT: // trap problem flags
// on first entry to this switch, it WILL timeout because we
// haven't requested crc/checksum mode yet. Do it now, and keep
// count, switching to checksum if necessary, as before.
// if the timeout occurs BETWEEN letters, our eventual NAK might
// just get it going again?.
if(++moderequests > RETRYMAX)
{
if(crcmode) // prepare for retries with checksum
{
cout << "No CRC mode ACK, switching to checksum " << endl;
crcmode = FALSE;
moderequests = 0;
}
else
{
cout << "Checksum mode request also failed. Aborting." << endl;
bad = TRUE;
}
}
SendChar(crcmode ? 'C': NAK); // request CRC or checksum mode from sender
break;
case ACK: // this is sender's reply to our 'C'/NAK
break; // do nothing more
case 'u': // I think this means start over from beginning
// 'u' is safe: all filename letters are uppercase
cout << "Retrying filename from beginning" << endl;
errors = 0; // error count
filename.remove(0); // fresh start
SendChar(crcmode ? 'C': NAK); // request CRC or checksum mode from sender
break;
case CTLZ: // CTLZ signals filename END.
{
filename += (uchar)ch; // the CTLZ goes into it also
ushort crc = 0; // cumulative checksum or CRC
for(int i = 0 ; i < filename.length() ; i++)
CalcCRC(crc, filename[i]);
if(crcmode)
crc_finish(crc);
SendCRC(crc); // send checksum or CRC
ch = ReceiveChar(1); // wait for ACK
if(ch == ACK)
done = TRUE;
else // it will send 'u' if wrong
{
cout << "Checksum error in filename" << endl;
if(++errors > RETRYMAX)
bad = TRUE;
else
{
cout << "Retrying filename from beginning" << endl;
errors = 0; // error count
filename.remove(0); // fresh start
SendChar(crcmode ? 'C': NAK); // request mode again
}
}
break;
} // end case CTLZ
case EOT: // no more filenames: we're completely done
delete[] bigbuf;
cout << "Batch receive complete" << endl;
return(filecount);
case ERR: // no diff: checksum must catch any error
default: // any valid char appends to filename
filename += (uchar)ch; // accumulate filename
// cout << (uchar)ch;
SendChar(ACK); // acknowledge EACH CHAR of the filename
break;
} // end switch
if(UserCancel()) // user quit
{
SendChar(CAN); // tell sender to cancel, too (put IN UserCancel?)
bad = TRUE;
}
} // while(!bad and !done)
if(filename.length())
{
// filename always arrives as 11 chars (8 + 3), no dot, with filename and ext
// each padded with trailing spaces if necessary, + CTLZ = 12 chars.
size_t i;
for(i = 0 ; i < filename.length() ; i++) // strip parity bits, if any
filename[i] = filename[i] & 0x7F;
if(filename[filename.length() - 1] == CTLZ) // strip the ending CTLZ
filename.remove(filename.length() - 1);
filename.insert(8,"."); // insert a period
while((i = filename.find(" ")) != NPOS) // remove all spaces
filename.remove(i,1);
}
} // end if(batch) {get filename}
if(!filename.length()) // originally empty, OR we accidentally emptied it
{
if(!bad) // on abort, empty filename is normal
cout << "Error: Filename is an empty string." << endl;
bad = TRUE;
}
if(bad)
break; // go to fn end for cleanup and return
done = FALSE; // for reuse later
//----------------------------------------------------------------------------
// we have a legal filename. Make sure it's ok to use the file.
if(batch)
filename.prepend(path); // prepend output path
filename.to_upper(); // upper case for messages
ofstream outfile(filename.c_str(),ios::noreplace | ios::binary);
if(!outfile)
{
if(yesno("Output file " + filename + " already exists. Overwrite"))
{
outfile.open(filename.c_str(),ios::binary);
if(!outfile) // user said overwrite, but it still failed.
{
cout << "File creation error: " << filename << endl;
break; // go to fn end for cleanup and return
}
}
else
{
cout << "Resolve conflict and try again." << endl;
break; // go to fn end for cleanup and return
}
}
//----------------------------------------------------------------------------
// file setup ok. Now download its sectors. The theory of operation here
// is that at each point there is a right answer and multiple wrong ones,
// handled by ifs and switches. As long as all is ok, you go deeper and
// deeper into the nested blocks. Success is at the deepest level.
// On error, you can set a flag if necessary, and MUST break out to
// redo the whole sector (or quit entirely).
cout << "Receiving: " << filename << endl;
// cout << "Awaiting Initial Sector... " << endl;
char* goodchars = bigbuf; // ptr to 1 char beyond last known-good char in bigbuf
uint sectorsreceived = 0; // number of sectors successfully received
uint tries = 0;
moderequests = 0;
crcmode = TRUE; // even if it failed above, try it again
while(!bad && !done) // wait for SOH, which signals sector # is coming next
{
ushort crc = 0; // crc accumulator
uint ch = ReceiveChar(4);
switch(ch)
{
case SOH: // this is what we want, and this block receives 1 sector
{
uint currentsector = ReceiveChar(1); // current sector number
uint seccmp = ReceiveChar(1); // sector number complement (~)
if((currentsector + seccmp) == 255) // it's correct IF they total 255
{
// if it's one more than the previous sector, it's correct!
// 0xff probably handles 255-0 or 255-1 transition, but is it right?
// here, they must both be handled as bytes.
if((currentsector & 0xff) == ((sectorsreceived + 1) & 0xff))
{
// good sectors will overwrite; any error should leave error msg
// so next sector starts on a new line.
// (sectorsreceived + 1) can count past 255.
cout << "\rSector #"
<< setw(5) << (sectorsreceived + 1) << ": ";
crc = 0; // reset crc
// receive exactly one sector of bytes, tentatively appending
// them into the sectors buffer (pending crc validation).
char* endpoint = goodchars + SECTORSIZE;
for(char* s = goodchars ; s < endpoint ; )
{
// top priority is catching chars as fast as they come in.
// bad ones of any type should be caught by crc error later.
// I also turned off stack overflow check in the project.
// and incrementing s here while we have it instead of at loop end
// may save a fetch of s.
// reliable, and imposes time limit
// *(s++) = (uchar)ReceiveChar(1);
// even faster, but program WILL hang up if chars stop coming.
// ok for local (2 computers in the same room) use,
// since you can type ^X locally,
// then type the needed chars on the H89 to cause loop exit,
// whereupon your ^X gets trapped and causes abort.
*(s++) = (uchar)_bios_serialcom(_COM_RECEIVE, 0, char());
}
// calculate crc on the entire sector during transmission pause
for(s = goodchars ; s < endpoint ; s++)
CalcCRC(crc, *s);
if(crcmode) // get crc info from sender
{
ReadAndCalcCRC(crc, 1);
ReadAndCalcCRC(crc, 1);
}
else
crc = crc - ReceiveChar(1);
// with either method, if crc is ZERO, sector is OK.
// this block is total success for the sector!
if(!crc)
{
tries = 0; // reset tries
sectorsreceived++;
// goodchars won't be changed again until we know if next char
// is SOH or EOT (unless buffer is full and written below),
// so we can use it as the end of text marker in EOT:
goodchars = endpoint; // appended chars are ok
// if buffer full, do disk write.
// sectorsreceived (a uchar) is ok to use if BUFSEC < 256
if((sectorsreceived % BUFSEC) == 0)
{
// buffer is full, so write it all
outfile.write(bigbuf, BUFSEC * SECTORSIZE);
if(outfile)
{
// both pointers restart at buffer beginning
goodchars = bigbuf;
// if sender times out while we write, it will
// resend the same sector. Hang here until it's done,
// then fall through and acknowledge
FlushIncoming();
}
else // serious error: just quit
{
cout << "Error writing file. Aborting" << endl;
bad = TRUE;
break; // break from switch case
}
}
SendChar(ACK); // signal that we got the sector ok
}
else // crc error, send NAK for retry
{
if(++tries > RETRYMAX)
{
cout << "Too many retries. Aborting." << endl;
bad = TRUE;
break; // break from switch case
}
else
{
cout << "Sector CRC/checksum error" << endl;
FlushIncoming(); // wait for silence
SendChar(NAK); // request retry
}
}
} // end if(this sector is 1 more than previous sect)
else // sector number RECEIVED ok, but it is the wrong ONE
{
if(currentsector == (sectorsreceived & 0xff))
{
cout << "Receiving and ignoring duplicate sector"
<< sectorsreceived << endl;
FlushIncoming(); // wait for silence on the line
SendChar(ACK); // pretend we received it
}
else // sector # received ok, but it's totally wrong number
{
cout << "Wrong sector #. Lost synchronization. Aborting." << endl;
bad = TRUE;
break;
}
}
} // end if((currentsector + seccmp) == 255)
else // transmission error in the sector # itself
{
if(++tries > RETRYMAX)
{
cout << "Too many retries. Aborting." << endl;
bad = TRUE;
break; // break from switch case
}
else // may have been the right number, so retry it.
{
cout << "Sector # header error. Retrying." << endl;
FlushIncoming(); // wait for silence
SendChar(NAK); // request retry
}
}
break;
} // end case SOH:
case CTLX: // cancel came instead
bad = TRUE;
break;
case EOT: // no more sectors: we're done.
outfile.write(bigbuf,(int)(goodchars - bigbuf)); // write the final block
if(outfile) // whole file is a success!
{
filecount++;
cout << "File receive complete" << endl << endl;
done = TRUE;
}
else // unrecoverable error: just quit
{
cout << "Error writing file, Aborting" << endl;
bad = TRUE;
}
SendChar(ACK); // report OK (even if not ok, sender can't help us)
break;
case TIMEOUT: // took too long
// on first entry to this switch, it WILL timeout because we
// haven't requested crc/checksum mode yet. Do it now, and keep
// count, switching to checksum if necessary, as before.
// sender does not acknowledge our request: just starts sending with SOH.
// if the timeout occurs BETWEEN sectors, our eventual NAK might
// just get it going again with the previous sector.
if(++moderequests > 5)
{
if(crcmode) // prepare for retries with checksum
{
cout << "No CRC mode acknowledge, switching to checksum " << endl;
crcmode = FALSE;
moderequests = 0;
}
else
{
cout << "Checksum mode request also failed. Aborting." << endl;
bad = TRUE;
}
}
SendChar(crcmode ? 'C': NAK); // request CRC or checksum mode from sender
break;
case ERR:
default: // any other non-SOH char is completely wrong. All you can do
// is wait through the sector data and send NAK to retry, which will
// happen automatically because the next sector #s won't match.
cout << "Received 0x" << hex << ch << dec << ", not SOH" << endl;
// The 1 second silence wait here is important because the binary
// data stream probably DOES have SOHs in it that you don't want to trap.
FlushIncoming();
break;
} // end switch(SOH or other chars)
if(UserCancel()) // user quit
bad = TRUE;
} // end while(!bad && !done)
outfile.close(); // close file so we can delete it if necessary
if(bad)
{
SendChar(CAN); // unsure if sender will actually cancel
cout << "Deleting partial file: " << filename << endl;
unlink(filename.c_str()); // delete it
batch = FALSE; // a bad file cancels the whole batch; you can't recover
} // if you try to continue, you get garbage for a filename.
if(!batch) // if not batch mode, we're done after 1 file
break;
} // while(1)
delete[] bigbuf;
return(filecount);
} //XMODEMReceiveFiles
//----------------------------------------------------------------------------
// on entry, pathname is the name of the file to send (full path, maybe with wildcards)
// returns the number of files successfully sent.
int SerialCom::XMODEMSendFiles(string& pathname, BOOL batch)
{
int filecount = 0; // number of files successfully sent
FileArray FileList(FALSE);
FileList.AddFile(pathname); // if wildcards, adds all matching files
while(1)
{
BOOL done = FALSE; // whether various sections are done
string filename; // the filename in OUR format
ifstream infile; // the file we're sending
//----------------------------------------------------------------------------
// make sure the file we're about to send exists and can be read.
// if not, skip and do next in list (if any).
while(FileList.GetItemsInContainer()) // if one fails, try next (if any)
{
filename = FileList.GetNext(); // the filename in OUR format
infile.open(filename.c_str(), ios::binary);
if(infile)
{
done = TRUE;
break;
}
else
cout << "Cannot open " << filename << endl;
}
if(!done) // if end of batch or we couldn't get a good file
{
if(batch) // this is the normal return point for batch mode
{
ReceiveChar(10); // wait until receiver starts sending NAK for next file
SendChar(ACK); // fake acknowledge their mode request
SendChar(EOT); // final EOT = end of batch
cout << "Batch send complete" << endl;
}
return(filecount); // either way, nothing left to send.
}
done = FALSE; // for reuse
//----------------------------------------------------------------------------
// if batch mode, send filename. if not batch, they already have it.
if(batch)
{
//----------------------------------------------------------------------------
// translate the name to weird transmission format (and with path stripped)
FilePathParser fp(filename);
string sendfile = fp.File; // copy to strings
string sendext = fp.Ext;
while(sendfile.length() < 8) // pad filename out to 8 chars
sendfile += " ";
if(sendext.length()) // remove period from extension
if(sendext[0] == '.')
sendext.remove(0,1);
while(sendext.length() < 3) // pad extension out to 3 chars
sendext += " ";
string sendfilename = sendfile + sendext + (uchar)CTLZ; // final form
sendfilename.to_upper(); // MUST be upper case
//----------------------------------------------------------------------------
// send the filename to the receiver, using error checking
while(!done)
{
cout << "Awaiting filename NAK/'C'" << endl;
ushort crc = 0; // checksum or CRC
while(1) // get mode request from receiver
{
uint j = ReceiveChar(10);
if((j == 'C') || (j == NAK)) // only one or the other is legal
{
crcmode = (j == 'C'); // 'C' = CRC, NAK = checksum
SendChar(ACK); // WE acknowledge their mode request
// precalc crc, depending on mode.
// our value to compare against only changes if receiver changes mode.
for(int i = 0 ; i < sendfilename.length() ; i++)
CalcCRC(crc, sendfilename[i]);
break; // we got what we needed...
}
if(UserCancel())
{
SendChar(CAN); // tell receiver to cancel, too (put IN UserCancel?)
return(filecount);
}
}
BOOL bad = FALSE; // if TRUE, filename send restarts
// send only up to the CTLZ: the required response is different for it.
for(int i = 0 ; i < (sendfilename.length()- 1) ; )
{
SendChar(sendfilename[i]); // send filename character
uint ch = ReceiveChar(2); // get ACK (hopefully)
if(ch == ACK) // receiver's reply to each filename character
i++; // prepare to send next one
else // anything else is wrong
{
cout << "Retrying filename from beginning" << endl;
bad = TRUE; // restart from beginning
break; // quit looping
}
} // end for(all chars except last)
if(!bad) // so far, so good...
{
SendChar(CTLZ); // send the CTLZ (it's always the last)
if(crcmode) // get crc or checksum
{
ushort crchi = (ushort)ReceiveChar(2); // get crc high byte
ushort crclo = (ushort)ReceiveChar(1); // get crc low byte
if(((crchi << 8) + crclo) == crc)
done = TRUE;
}
else
{
if((ushort)ReceiveChar(2) == crc) // get checksum
done = TRUE;
}
}
if(UserCancel()) // user quit
{
SendChar(CAN); // tell receiver to cancel, too (put IN UserCancel?)
return(filecount);
}
SendChar(done ? ACK : 'u'); // means OK or redo from start
} // while(!done)
} // end if(batch)
//----------------------------------------------------------------------------
// send the file sectors
cout << endl;
cout << "Sending: " << filename << endl;
cout << "Awaiting NAK/'C'" << endl;
while(1) // get mode request from receiver
{
uint j = ReceiveChar(10);
if((j == 'C') || (j == NAK)) // only one or the other is legal
{
crcmode = (j == 'C'); // 'C' = CRC, NAK = checksum
break; // we got what we needed...
}
if(UserCancel())
return(filecount);
}
int tries = 0; // # of times current sector was tried (failed)
uint currentsector = 1; // # of sector being sent
// these may be needed for batch mode, when one break won't get completely out of loop
BOOL bad = FALSE; // if TRUE, whole procedure aborts.
done = FALSE; // whether various sections are done
while(!bad && !done)
{
// As soon as we get receiver's 'C' or NAK, we send (no wait or acknowledgment):
// SOH, AND the 2 sector count chars, AND the sector data, AND the crc/checksum.
// The only complaint the receiver can make is to wait for the
// sector end and send NAK (for retry).
// Under all circumstances SENDER only aborts if retry limit is exceeded.
// (Receiver can't request abort.)
// start a sector. good sectors overwrite. bad leaves error msg and starts new line.
cout << "\rSector #" << setw(5) << currentsector << ": ";
SendChar(SOH);
SendChar((uchar)currentsector); // strips high byte for 0-255 only
SendChar(~((uchar)currentsector)); // complement used for error check
ushort crc = 0;
uchar ch; // file char to be sent
for(int i = 0 ; i < SECTORSIZE ; i++)
{
// method slow, but we're the sender; shouldn't matter
if(infile.get(ch)) // MSDOS will read and buffer larger chunks for us.
{
CalcCRC(crc, ch); // accumulate crc
SendChar(ch); // send the char
}
else // reached end of file, but the protocol requires
{ // whole sectors, so pad to end.
// CPM pads with whatever data is actually after the CTLZ in the sector.
// we pad with CTLZ. If file ended at a sector boundary, there
// will be NO ctlz pads, but Mex will probably add a ctlz when it writes
// last sector.
ch = CTLZ; // so CPM knows where exact EOF is.
CalcCRC(crc, ch); // accumulate crc
SendChar(ch); // send the char
}
} // end for(send 1 sector)
if(crcmode) // finishing calcs for crc only
crc_finish(crc);
SendCRC(crc); // always send the crc or checksum
if(ReceiveChar(10) == ACK) // if it was ok,
{
tries = 0; // reset retry counter
currentsector++; // start a new sector (if any more)
// peek prevents sending an empty extra sector: if the file ended on an exact
// sector boundary in the for() above, infile hasn't failed yet.
if(!infile || (infile.peek() == EOF)) // finished file
{
SendChar(EOT); // send EOT instead of SOH
if(ReceiveChar(10) == ACK) // need response
{
cout << "File send complete" << endl << endl;
filecount++;
done = TRUE;
}
else
{
cout << "No ACK of End of File! Aborting." << endl;
bad = TRUE;
}
break; // break to redo while(1)
} // end if(finished file)
}
else // receiver had errors: must retry same sector.
{
if(++tries > RETRYMAX)
{
cout << "Too many sector retries. Aborting." << endl;
break; // break to fn end and quit
}
else
cout << "Received NAK not ACK. Retrying sector." << endl;
}
if(UserCancel())
bad = TRUE;
} // while(!bad && !done)
if(!batch) // if not batch mode, we're done after 1 file
break; // break to fn end
} // while(1)
return(filecount); // normal return point for nonbatch mode
} //XMODEMSendFiles
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
// Hayes Modem functions
//////////////////////////////////////////////////////////////////////////////
//----------------------------------------------------------------------------
// reset Hayes modem. There will be at least a 2 sec. wait.
void SerialCom::HayesReset()
{
SendString("\r"); // if modem is in an error state, this starts fresh
FlushIncoming(); // while chars come in, read and discard them
SendString("ATZ\r"); // reset
FlushIncoming(); // while chars come in, read and discard them
HayesVerbose = HayesEcho = TRUE;
} //HayesReset
//----------------------------------------------------------------------------
// send command string to Hayes modem, and wait for result code.
void SerialCom::HayesCommand(const string& command)
{
FlushIncoming(); // while chars come in, read and discard them
// V commands take effect immediately, so change setting before the command
// so resultwait will work properly.
if(command.contains("V")) // V1, V0, or Vblank means V0
HayesVerbose = FALSE;
if(command.contains("V1")) // but V1 overrides
HayesVerbose = TRUE;
SendString(command + "\r"); // issue the command
HayesResultwait(); // wait for the result code
// E commands don't affect the command line that contains them. If echo
// was on, the command was echoed, so don't change settings until after.
if(command.contains("E")) // E1, E0, or Eblank means E0
HayesEcho = FALSE;
if(command.contains("E1")) // but E1 overrides
HayesEcho = TRUE;
} //HayesCommand
//----------------------------------------------------------------------------
// wait for the \r from modem's NON-verbose result code string
// or for the second /n from a verbose result code string
void SerialCom::HayesResultwait()
{
uint ch;
int count = 0;
if(HayesVerbose) // verbose result = \r\nRESULT\r\n
{
while(1)
{
ch = ReceiveChar(1);
// cerr << "(" << ch << " " << (iscntrl(ch) ? ' ' : (char)ch) << ") ";
if(ch == '\n')
if(++count == 2)
break;
}
}
else // nonverbose result = digit\r
{
// If input is being echoed, our command-terminating CR comes back,
// so we have to wait for the next one. Not a problem in verbose mode,
// because in that case Resultwait waits for LF, not CR.
int crneed = HayesEcho ? 2 : 1;
while(1)
{
ch = ReceiveChar(1);
// cerr << "(" << ch << " " << (iscntrl(ch) ? ' ' : (char)ch) << ") ";
if(ch == '\r')
if(++count == crneed)
break;
}
}
delay(100); // modem isn't quite ready for input the instant it sends OK
} //HayesResultwait
//----------------------------------------------------------------------------
// end class SerialCom
//////////////////////////////////////////////////////////////////////////////
A modem program that uses the SerialCom class, which provides most of its functionality.
/* smodem.cpp 10-24-99
Copyright (C)1999 Steven Whitney.
Published under GNU GPL (General Public License) Version 2, with ABSOLUTELY NO WARRANTY.
------
TO DO:
when batch send reports that it is finished, restate the user's command, for reference.
Ex: "Finished sending batch *.DOC"
P5-90 computer:
receive works at 4800 with few errors,
It CAN receive with no errors at 9600 when Windows is not running.
Send at 4800 is borderline, 9600 NO: receiver's problem?
Mex can't RECEIVE at 9600 with any other pgm, either.
still doesn't seem right that a 90mh computer with an ACE probably rated
at 38,400 or higher can't keep up with a 2mh computer at 9600!
a Windows version (for file transfer only) would be convenient,
with file selection for send from a dialog box.
sometime, test xmodem transfer between 2 running copies of terminal.exe
(one on each COM port) at 9600. can they do it?
the occasional error resolved by retry is rare.
most errors are spectacular, multiple, and catastrophic.
during some receives there have been sector-long pauses without error msgs (and w/o errors).
H89 calculating crc or something?
A possible enhancement would be to allow using this as a remote terminal to H89 or H100
by intercepting H19 terminal codes and translating locally to the appropriate actions.
(Graphics chars a particular challenge.) But low priority, not actually worth doing.
------
Notes:
--Only testing can tell if the xmodem protocol is properly implemented.
--all usual CTL keys except ^X are now ALT, so control keys can be passed through and sent.
--error checking works: when errors occur, received file is correct.
*/
#include <dos.h>
#include <math.h>
#include <classlib\time.h>
#include "c:\bcs\my.h"
#pragma hdrstop
#include "c:\bcs\mylib.cpp"
#include "c:\bcs\library\serialco.cpp"
//////////////////////////////////////////////////////////////////////////////
#define BUFFSIZ 0x4000 // size of text CAPTURE buffer (16k)
//////////////////////////////////////////////////////////////////////////////
// Globals
//----------------------------------------------------------------------------
// here so ShowHelp can display their values
BOOL capture = FALSE;
BOOL echo = FALSE;
BOOL PrintFlag = FALSE;
SerialCom sc(0, 4800, 8, 1, 0);
// used in multiple locs
char *capturebuf = 0; // text capture buffer
uint TxtPtr = 0; // number of bytes in capture buffer
//----------------------------------------------------------------------------
void SetBaudRate()
{
static int baudtable[4] = {1200,2400,4800,9600};
int newbaud = sc.GetBaud();
userinput("[1200,2400,4800,9600] Enter baud rate ", newbaud);
for(int i = 0 ; i < 4 ; i++)
if(newbaud == baudtable[i])
{
sc.InitComPort(0, newbaud, 8, 1, 0);
cout << sc << endl;
presskey();
return;
}
cout << "Invalid baud rate. No change." << endl;
} //SetBaudRate
//----------------------------------------------------------------------------
// help for terminal mode
void ShowTermHelp()
{
cout << "--------------------------------------------------" << endl;
cout << "TERMINAL MODE HELP:" << endl;
cout << "ALT+A Go to Command Mode" << endl;
cout << "ALT+E Echo, now " << (echo ? "ON" : "OFF") << endl;
cout << "ALT+H Help for terminal mode" << endl;
cout << "ALT+P Printer, now " << (PrintFlag ? "ON" : "OFF") << endl;
cout << "ALT+R Reset modem" << endl;
cout << "CTL+X Cancel send/recv" << endl;
cout << endl;
}
//----------------------------------------------------------------------------
void DoCommand()
{
string filename;
BOOL batchmode = FALSE;
string cmdline;
while(1)
{
ShowTermHelp();
cout << "--------------------------------------------------" << endl;
cout << "You are now in COMMAND MODE:" << endl;
cout << endl;
cout << "C (" << (capture ? "ON " : "OFF") << ") Capture received data to buffer" << endl;
cout << "B Change baud rate" << endl;
cout << "K Save captured data" << endl;
cout << "Q Exit program" << endl;
cout << "R Receive file" << endl;
cout << "S Send file" << endl;
cout << "T Enter terminal mode" << endl;
cout << endl;
cout << "Command: ";
getline(cin,cmdline,'\n'); // get user command
cmdline.to_upper();
char cmd = 0; // reset each pass
if(cmdline.length())
cmd = cmdline[0]; // first char is the single char command
switch(cmd)
{
// no ALT key cases: getline() above probably can't read them anyway
case 'B': // change the baud rate
SetBaudRate();
break;
case 'C': // toggle text capture
capture = !capture;
cout << "Capture now" << (capture ? "ON" : "OFF, ")
<< (BUFFSIZ - TxtPtr) << " bytes free" << endl;
break;
case 'K': // keep text buffer
if(!TxtPtr)
cout << "Nothing to save" << endl;
else
{
userinput("Save as what file", filename);
// ok for now, but you should ask for a name at pgm start,
// then append to that file whenever the buffer fills up.
// and eliminate Keep command.
ofstream outfile(filename.c_str(), ios::binary | ios::app);
if(outfile)
{
outfile.write(capturebuf, TxtPtr);
capture = FALSE;
TxtPtr = 0;
}
else
cout << "File Write Error. Try again." << endl;
}
break;
case 'R': // receive file
batchmode = FALSE;
if((cmdline.length() > 1) && (toupper(cmdline[1]) == 'B')) // RB allowed
batchmode = TRUE;
else
batchmode = yesno("Batch mode");
// sc.crcmode = yesno("CRC mode");
userinput(batchmode ? "Destination path" : "Receive what file", filename);
sc.XMODEMReceiveFiles(filename, batchmode);
presskey();
filename.remove(0); // a nuissance when it's held over
break;
case 'S': // send file
batchmode = FALSE;
if((cmdline.length() > 1) && (toupper(cmdline[1]) == 'B')) // SB allowed
batchmode = TRUE;
else
batchmode = yesno("Batch mode");
userinput("Send what file (* and ? ok)", filename);
if(!batchmode)
if(filename.contains("?") || filename.contains("*"))
{
cout << "You need batch mode for wildcards. Try again." << endl;
break;
}
sc.XMODEMSendFiles(filename, batchmode);
presskey();
filename.remove(0);
break;
case 'Q': // quit program
if(!yesno("Quit program"))
break;
if(capturebuf)
delete[] capturebuf;
exit(0);
case 'T': // enter terminal mode
cout << "Entering terminal mode..." << endl;
return;
default:
cout << "Invalid command, try again" << endl;
break;
}
} // while(1)
} // DoCommand
//----------------------------------------------------------------------------
//////////////////////////////////////////////////////////////////////////////
void main()
{
string::set_case_sensitive(0);
string::set_paranoid_check(1);
capturebuf = new char[BUFFSIZ];
ofstream printer; // used when echoing to printer
sc.displaymode = SerialCom::BRIEF; // or FULL;
sc.ResetComPort();
sc.FlushIncoming();
uint ch = ALTA; // so first entry displays menu
while(1) // THIS LOOP IS TERMINAL MODE
{
if(csts()) // get any char at keyboard
ch = ci();
if(ch)
switch(ch) // terminal mode commands
{
case ALTA:
DoCommand(); // this is command mode
break;
case ALTH: // HELP
ShowTermHelp();
break;
case ALTP: // toggle echo to printer
if(PrintFlag) // if it's on, turn off
printer.close();
if(!PrintFlag) // if it's off, turn ON
printer.open("PRN:");
PrintFlag = !PrintFlag; // now change setting to match
break;
case ALTQ: // quit
if(!yesno("Quit program"))
break;
if(capturebuf)
delete[] capturebuf;
exit(0);
case ALTE: // local echo of keyboard data
echo = !echo;
break;
case ALTR: // Reset modem
sc.ResetComPort();
break;
default: // send user-typed text through modem
sc.SendChar(ch);
if(echo)
{
// outgoing should be diff color from incoming, but it requires constream.
// since serialcom outputs to cout, the 2 cursor locs may differ,
// garbling screen display.
cout << TranslateControls((uchar)ch); // show translated
// cout << (uchar)ch; // show normal
// you want both sides of dialog in the capture buffer.
// if local echo is ON, then presumably remote is not echoing,
// so we must add our own output to the capture buffer.
// if local echo is OFF, it gets written there when it comes back below.
if(capture)
if(TxtPtr < BUFFSIZ)
capturebuf[TxtPtr++] = ch;
else
cout << "Capture buffer overflow" << endl;
}
if(PrintFlag) // and maybe to printer
printer << ((ch == CR) ? '\n' : ch);// file write will expand to crlf
break;
}
ch = 0; // reset it
if(sc.CharWaiting()) // incoming chars ready?
{
uint inchar = sc.ReceiveChar(1); // get it (no wait: we know there's one waiting)
// #error useful during testing, but note that it translates <CR><LF>
// to those strings rather than couting them.
cout << TranslateControls((uchar)inchar); // show translated
// cout << (uchar)inchar; // show normal
cout << "=" << hex << inchar << dec << " ";
if(capture)
if(TxtPtr < BUFFSIZ)
capturebuf[TxtPtr++] = inchar;
else
cout << "Capture buffer overflow" << endl;
if(PrintFlag)
printer << ((inchar == CR) ? '\n' : inchar);
}
} // end while
} // main
//----------------------------------------------------------------------------
|
|
|
|
|
|